Java对象的创建过程:类的初始化与实例化
⼀、Java对象创建时机
我们知道,⼀个对象在可以被使⽤之前必须要被正确地实例化。在Java代码中,有很多⾏为可以引起对象的创建,最为直观的⼀种就是使⽤new关键字来调⽤⼀个类的构造函数显式地创建对象,这种⽅式在Java规范中被称为 : 由执⾏类实例创建表达式⽽引起的对象创建。除此之外,我们还可以使⽤反射机制(Class类的newInstance⽅法、使⽤Constructor类的newInstance⽅法)、使⽤Clone⽅法、使⽤反序列化等⽅式创建对象。
1). 使⽤new关键字创建对象
这是我们最常见的也是最简单的创建对象的⽅式,通过这种⽅式我们可以调⽤任意的构造函数(⽆参的和有参的)去创建对象。⽐如:
Student student = new Student();
2). 使⽤Class类的newInstance⽅法(反射机制)
我们也可以通过Java的反射机制使⽤Class类的newInstance⽅法来创建对象,事实上,这个newInstance⽅法调⽤⽆参的构造器创建对象,⽐如:
Student student2 = (Student)Class.forName("Student类全限定名").newInstance();
或者:
Student stu = wInstance();
3). 使⽤Constructor类的newInstance⽅法(反射机制)
lect.Constructor类⾥也有⼀个newInstance⽅法可以创建对象,该⽅法和Class类中的newInstance⽅法很像,但是相⽐之下,Constructor类的newInstance⽅法更加强⼤些,我们可以通过这个newInstance⽅法调⽤有参数的和私有的构造函数,⽐如:
public class Student {
private int id;
public Student(Integer id) {
this.id = id;
}
public static void main(String[] args) throws Exception {
Constructor<Student> constructor = Student.class
.getConstructor(Integer.class);
Student stu3 = wInstance(123);
}
}
使⽤newInstance⽅法的这两种⽅式创建对象使⽤的就是Java的反射机制,事实上Class的newInstance⽅法内部调⽤的也是Constructor的newInstance⽅法。
4). 使⽤Clone⽅法创建对象
⽆论何时我们调⽤⼀个对象的clone⽅法,JVM都会帮我们创建⼀个新的、⼀样的对象,特别需要说明的是,⽤clone⽅法创建对象的过程中并不会调⽤任何构造函数。关于如何使⽤clone⽅法以及浅克隆/深克隆机制,笔者已经在博⽂做了详细的说明。简单⽽⾔,要想使⽤clone⽅法,我们就必须先实现Cloneable接⼝并实现其定义的clone⽅法,这也是原型模式的应⽤。⽐如:
public class Student implements Cloneable{
private int id;
public Student(Integer id) {
this.id = id;
}
@Override
protected Object clone() throws CloneNotSupportedException {
// TODO Auto-generated method stub
return super.clone();
}
public static void main(String[] args) throws Exception {
Constructor<Student> constructor = Student.class
.getConstructor(Integer.class);
Student stu3 = wInstance(123);
Student stu4 = (Student) stu3.clone();
}
}
5). 使⽤(反)序列化机制创建对象
当我们反序列化⼀个对象时,JVM会给我们创建⼀个单独的对象,在此过程中,JVM并不会调⽤任何构造函数。为了反序列化⼀个对象,我们需要让我们的类实现Serializable接⼝,⽐如:
public class Student implements Cloneable, Serializable {
private int id;
public Student(Integer id) {
this.id = id;
}
@Override
public String toString() {
return"Student [id=" + id + "]";
}
public static void main(String[] args) throws Exception {
java接口可以创建对象吗Constructor<Student> constructor = Student.class
.getConstructor(Integer.class);
Student stu3 = wInstance(123);
// 写对象
ObjectOutputStream output = new ObjectOutputStream(
new FileOutputStream("student.bin"));
output.writeObject(stu3);
output.close();
// 读对象
ObjectInputStream input = new ObjectInputStream(new FileInputStream(
"student.bin"));
Student stu5 = (Student) adObject();
System.out.println(stu5);
}
}
6). 完整实例
public class Student implements Cloneable, Serializable {
private int id;
public Student() {
}
public Student(Integer id) {
this.id = id;
}
@Override
protected Object clone() throws CloneNotSupportedException {
// TODO Auto-generated method stub
return super.clone();
}
@Override
public String toString() {
return"Student [id=" + id + "]";
}
public static void main(String[] args) throws Exception {
System.out.println("使⽤new关键字创建对象:");
Student stu1 = new Student(123);
System.out.println(stu1);
System.out.println("\n---------------------------\n");
System.out.println("使⽤Class类的newInstance⽅法创建对象:");
Student stu2 = wInstance(); //对应类必须具有⽆参构造⽅法,且只有这⼀种创建⽅式 System.out.println(stu2);
System.out.println("\n---------------------------\n");
System.out.println("使⽤Constructor类的newInstance⽅法创建对象:");
Constructor<Student> constructor = Student.class
.getConstructor(Integer.class); // 调⽤有参构造⽅法
Student stu3 = wInstance(123);
System.out.println(stu3);
System.out.println("\n---------------------------\n");
System.out.println("使⽤Clone⽅法创建对象:");
Student stu4 = (Student) stu3.clone();
System.out.println(stu4);
System.out.println("\n---------------------------\n");
System.out.println("使⽤(反)序列化机制创建对象:");
// 写对象
ObjectOutputStream output = new ObjectOutputStream(
new FileOutputStream("student.bin"));
output.writeObject(stu4);
output.close();
// 读取对象
ObjectInputStream input = new ObjectInputStream(new FileInputStream(
"student.bin"));
Student stu5 = (Student) adObject();
System.out.println(stu5);
}
}/* Output:
使⽤new关键字创建对象:
Student [id=123]
---------------------------
使⽤Class类的newInstance⽅法创建对象:
Student [id=0]
---------------------------
使⽤Constructor类的newInstance⽅法创建对象:
Student [id=123]
---------------------------
使⽤Clone⽅法创建对象:
Student [id=123]
---------------------------
使⽤(反)序列化机制创建对象:
Student [id=123]
*///:~
从Java虚拟机层⾯看,除了使⽤new关键字创建对象的⽅式外,其他⽅式全部都是通过转变为invokevirtual指令直接创建对象的。
⼆. Java 对象的创建过程
当⼀个对象被创建时,虚拟机就会为其分配内存来存放对象⾃⼰的实例变量及其从⽗类继承过来的实例变量(即使这些从超类继承过来的实例变量有可能被隐藏也会被分配空间)。在为这些实例变量分配内存的同时,这些实例变量也会被赋予默认值(零值)。在内存分配完成之后,Java虚拟机就会开始对新创建的对象按照程序猿的意志进⾏初始化。在Java对象初始化过程中,主要涉及三种执⾏对象初始化的结构,分别是实例变量初始化、实例代码块初始化以及构造函数初始化。
1、实例变量初始化与实例代码块初始化
我们在定义(声明)实例变量的同时,还可以直接对实例变量进⾏赋值或者使⽤实例代码块对其进⾏赋值。如果我们以这两种⽅式为实例变量进⾏初始化,那么它们将在构造函数执⾏之前完成这些初始化操作。实际上,如果我们对实例变量直接赋值或者使⽤实例代码块赋值,那么编译器会将其中的代码放到类的构造函数中去,并且这些代码会被放在对超类构造函数的调⽤语句之后(还记得吗?Java要求构造函数的第⼀条语句必须是超类构造函数的调⽤语句),构造函数本⾝的代码之前。例如:
public class InstanceVariableInitializer {
private int i = 1;
private int j = i + 1;
public InstanceVariableInitializer(int var){
System.out.println(i);
System.out.println(j);
this.i = var;
System.out.println(i);
System.out.println(j);
}
{ // 实例代码块
j += 3;
}
public static void main(String[] args) {
new InstanceVariableInitializer(8);
}
}/* Output:
1
5
8
5
*///:~
上⾯的例⼦正好印证了上⾯的结论。特别需要注意的是,Java是按照编程顺序来执⾏实例变量初始化器和实例初始化器中的代码的,并且不允许顺序靠前的实例代码块初始化在其后⾯定义的实例变量,⽐如:
public class InstanceInitializer {
{
j = i;
}
private int i = 1;
private int j;
}
public class InstanceInitializer {
private int j = i;
private int i = 1;
}
上⾯的这些代码都是⽆法通过编译的,编译器会抱怨说我们使⽤了⼀个未经定义的变量。之所以要这
么做是为了保证⼀个变量在被使⽤之前已经被正确地初始化。但是我们仍然有办法绕过这种检查,⽐如:
public class InstanceInitializer {
private int j = getI();
private int i = 1;
public InstanceInitializer() {
i = 2;
}
private int getI() {
return i;
}
public static void main(String[] args) {
InstanceInitializer ii = new InstanceInitializer();
System.out.println(ii.j);
}
}
如果我们执⾏上⾯这段代码,那么会发现打印的结果是0。因此我们可以确信,变量j被赋予了i的默认值0,这⼀动作发⽣在实例变量i初始化之前和构造函数调⽤之前。
2、构造函数初始化
我们可以从上⽂知道,实例变量初始化与实例代码块初始化总是发⽣在构造函数初始化之前,那么我们下⾯着重看看构造函数初始化过程。众所周知,每⼀个Java中的对象都⾄少会有⼀个构造函数,如果我们没有显式定义构造函数,那么它将会有⼀个默认⽆参的构造函数。在编译⽣成的字节码中,这些构造函数会被命名成<init>()⽅法,参数列表与Java语⾔书写的构造函数的参数列表相同。
我们知道,Java要求在实例化类之前,必须先实例化其超类,以保证所创建实例的完整性。事实上,这⼀点是在构造函数中保证的:Java强制要求Object 对象(Object是Java的顶层对象,没有超类)之外
的所有对象构造函数的第⼀条语句必须是超类构造函数的调⽤语句或者是类中定义的其他的构造函数,如果我们既没有调⽤其他的构造函数,也没有显式调⽤超类的构造函数,那么编译器会为我们⾃动⽣成⼀个对超类构造函数的调⽤,⽐如:
public class ConstructorExample {
}
对于上⾯代码中定义的类,我们观察编译之后的字节码,我们会发现编译器为我们⽣成⼀个构造函数,如下,
aload_0
invokespecial #8; //Method java/lang/Object."<init>":()V
return
上⾯代码的第⼆⾏就是调⽤Object类的默认构造函数的指令。也就是说,如果我们显式调⽤超类的构造函数,那么该调⽤必须放在构造函数所有代码的最前⾯,也就是必须是构造函数的第⼀条指令。正因为如此,Java才可以使得⼀个对象在初始化之前其所有的超类都被初始化完成,并保证创建⼀个完整的对象出来。
特别地,如果我们在⼀个构造函数中调⽤另外⼀个构造函数,如下所⽰,
public class ConstructorExample {
private int i;
ConstructorExample() {
this(1);
....
}
ConstructorExample(int i) {
....
this.i = i;
....
}
}
对于这种情况,Java只允许在ConstructorExample(int i)内调⽤超类的构造函数,也就是说,下⾯两种情形的代码编译是⽆法通过的:
public class ConstructorExample {
private int i;
ConstructorExample() {
super();
this(1); // Error:Constructor call must be the first statement in a constructor
....
}
ConstructorExample(int i) {
....
this.i = i;
....
}
}
或者,
public class ConstructorExample {
private int i;
ConstructorExample() {
this(1);
super(); //Error: Constructor call must be the first statement in a constructor
.
...
}
ConstructorExample(int i) {
this.i = i;
}
}
Java通过对构造函数作出这种限制以便保证⼀个类的实例能够在被使⽤之前正确地初始化。
3、⼩结
总⽽⾔之,实例化⼀个类的对象的过程是⼀个典型的递归过程,如下图所⽰。进⼀步地说,在实例化⼀个类的对象时,具体过程是这样的:
在准备实例化⼀个类的对象前,⾸先准备实例化该类的⽗类,如果该类的⽗类还有⽗类,那么准备实例化该类的⽗类的⽗类,依次递归直到递归到Object 类。此时,⾸先实例化Object类,再依次对以下
各类进⾏实例化,直到完成对⽬标类的实例化。具体⽽⾔,在实例化每个类时,都遵循如下顺序:先依次执⾏实例变量初始化和实例代码块初始化,再执⾏构造函数初始化。也就是说,编译器会将实例变量初始化和实例代码块初始化相关代码放到类的构造函数中去,并且这些代码会被放在对超类构造函数的调⽤语句之后,构造函数本⾝的代码之前。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论