平常我们用的最多的就是使用new
来创建对象了,这是一种方式;如果我们使用框架的话就直接交给Spring
去管理就好了,Spring 底层是使用反射来创建对象的。其他的,知道的基本就少了,下面我们来看看Java中创建对象的方式。
先看一点题外并相关的内容,JVM中的5种方法调用指令:
-
invokestatic
:该指令用于调用静态方法,即使用 static 关键字修饰的方法; -
invokespecial
:该指令用于三种场景:调用实例构造方法,调用私有方法(即 private 关键字修饰的方法)和父类方法(即 super 关键字调用的方法); -
invokeinterface
:该指令用于调用接口方法,在运行时再确定一个实现此接口的对象; -
invokevirtual
:该指令用于调用虚方法(就是除了上述三种情况之外的方法); - `invokedynamic1:在运行时动态解析出调用点限定符所引用的方法之后,调用该方法;在 JDK 1.7 中提出,主要用于支持 JVM 上的动态脚本语言(如 Groovy,Jython 等)。
1. 使用new
关键字来创建对象
使用 new 来创建对象是最简单的一种方式了,new 是 Java 中的关键字,new 通过为新对象分配内存并返回对该内存的引用来实例化一个类,这个实例化一个类其实就相当于创建了一个对象,因为类也是一种对象;new 也负责调用对象的构造函数,下面是使用 new 来创建对象的代码
Object obj = new Object();
这段代码中,我们在堆区域中分配了一块内存,然后把 obj 对象指向了这块内存区域。
查看一下编译后的字节码
![](https://img.haomeiwen.com/i22972070/e45fe24585f3b557.png)
可以看出,新建对象经历了
new
dup
invokespecial
astore_1
4步指令后才返回。
在 Java 中,我们认为创建一个对象就是调用其构造方法,所以我们使用 new Object() 构造的对象,其实是调用了 Object 类的无参数 的构造方法。但是通过字节码我们发现,对象的创建和调用其构造方法是分开的。
-
new
表示在堆中创建一个对象,并把对象的引用推入栈中 -
dup
会复制栈上的最后一个元素,然后再次将其推入栈;因此,如果在栈上有一个对象引用,并且调用了 dup,则现在在栈上有对该对象的两个引用。
关于dup
指令的问题,参考关于JVM字节码中dup指令的问题 -
invokespecial
表示调用对象无参数的构造方法进行初始化。JVM指令集中,invokespecial指令格式如下:
invokespecial.png
看一下操作数栈,需要一个objectref引用(对象的地址),后面是可选的参数;由于初始化没有返回值,调用之后没有东西入栈(用...表示没有入栈)。没有返回值,调用之后没有东西入栈(用...表示没有入栈)。 -
astore_1
就会把操作数栈顶的那个引用消耗掉,保存到指定的局部变量去。
若不创建局部变量,只新建对象,
public static void main(String[] args) {
new Object();
}
字节码:
![](https://img.haomeiwen.com/i22972070/691f31d9da3d7669.png)
上图中的 astore_1 竟然变成了 pop。
![](https://img.haomeiwen.com/i22972070/e621834597581557.png)
这也就是说,new Object() 没有保存对象的局部变量,而是直接把它给消耗掉了。
2. 使用Class
类newInstance()
方法来创建对象
这个newInstance()
方法指的是Class
类中的方法,newInstance()
方法会调用无参的构造方法创建对象。
public static void main(String[] args) throws Exception {
CodeMsg cm = (CodeMsg) Class.forName("com.test.domain.CodeMsg").newInstance();
// 或者使用
CodeMsg cm1 = CodeMsg.class.newInstance();
}
上面是newInstance使用的2种方式,第1种多调了forName()
方法,forName()
是一个静态方法
![](https://img.haomeiwen.com/i22972070/9af2b60cd887020c.png)
编译后结果:
![](https://img.haomeiwen.com/i22972070/da8f8b42ebcde599.png)
可以看出第一种方式多调用了
invokestatic
指令
-
ldc
的意思是把常量池中的引用推入到当前堆栈中 -
checkcast
含义就是进行类型转换,因为 newInstance 生成的是一个 Object 的对象,所以我们需要把它转换为我们指定的类型
new和newInstance区别:
-
newInstance()
: 弱类型,低效率,只能调用无参构造。
使用newInstance()方法的时候,必须保证这个类已经加载,这由Class
类的静态方法forName()
完成 -
new
: 强类型,相对高效,能调用任何public构造。
3. 使用Constructor
类的newInstance()
方法创建对象
和Class
类的newInstance
方法很像,java.lang.reflect.Constructor
类里也有一个newInstance
方法可以创建对象。我们可以通过这个newInstance方法调用有参数的和私有的构造函数。
Constructor<CodeMsg> constructor = CodeMsg.class.getConstructor();
CodeMsg cm = constructor.newInstance();
这两种newInstance
方法就是大家所说的反射。事实上Class
的newInstance
方法内部调用Constructor
的newInstance
方法。这也是众多框架,如Spring、Hibernate、Struts等使用后者的原因。
4. 使用clone()
方法创建对象
无论何时我们调用一个对象的clone方法,jvm就会创建一个新的对象,将前面对象的内容全部拷贝进去。用clone方法创建对象并不会调用任何构造函数。
要使用clone方法,我们需要先实现Cloneable接口并实现其定义的clone方法。
public class Person implements Cloneable{
private int age ;
private String name;
public Person(int age, String name) {
this.age = age;
this.name = name;
}
public Person() {}
public int getAge() {
return age;
}
public String getName() {
return name;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return (Person)super.clone();
}
}
//////////////////////////////////////////
public static void main(String[] args) {
Person p = new Person(23, "zhang");
Person p1 = (Person) p.clone();
}
5. 使用反序列化创建对象
当我们序列化和反序列化一个对象,jvm会给我们创建一个单独的对象。在反序列化时,jvm创建对象并不会调用任何构造函数。
为了反序列化一个对象,我们需要让我们的类实现Serializable接口。
参考文章:
https://mp.weixin.qq.com/s/plSHSWe491Q4KJhhNax2Zg
https://www.cnblogs.com/wxd0108/p/5685817.html
网友评论