一个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 = Student.class.newInstance();

3). 使用Constructor类的newInstance方法(反射机制)

java.lang.relect.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 constructor = Student.class

.getConstructor(Integer.class);

Student stu3 = constructor.newInstance(123);

}

}

使用newInstance方法的这两种方式创建对象使用的就是Java的反射机制,事实上Class的newInstance方法内部调用的也是Constructor的newInstance方法。

4). 使用Clone方法创建对象

无论何时我们调用一个对象的clone方法,JVM都会帮我们创建一个新的、一样的对象,特别需要说明的是,用clone方法创建对象的过程中并不会调用任何构造函数。关于如何使用clone方法以及浅克隆/深克隆机制,笔者已经在博文《 Java String 综述(下篇)》做了详细的说明。简单而言,要想使用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 constructor = Student.class

.getConstructor(Integer.class);

Student stu3 = constructor.newInstance(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 {

Constructor constructor = Student.class

.getConstructor(Integer.class);

Student stu3 = constructor.newInstance(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) input.readObject();

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 = Student.class.newInstance(); //对应类必须具有无参构造方法,且只有这一种创建方式

System.out.println(stu2);

System.out.println("\n---------------------------\n");

System.out.println("使用Constructor类的newInstance方法创建对象:");

Constructor constructor = Student.class

.getConstructor(Integer.class); // 调用有参构造方法

Student stu3 = constructor.newInstance(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) input.readObject();

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中的对象都至少会有一个构造函数,如果我们没有显式定义构造函数,那么它将会有一个默认无参的构造函数。在编译生成的字节码中,这些构造函数会被命名成()方法,参数列表与Java语言书写的构造函数的参数列表相同。

我们知道,Java要求在实例化类之前,必须先实例化其超类,以保证所创建实例的完整性。事实上,这一点是在构造函数中保证的:Java强制要求Object对象(Object是Java的顶层对象,没有超类)之外的所有对象构造函数的第一条语句必须是超类构造函数的调用语句或者是类中定义的其他的构造函数,如果我们既没有调用其他的构造函数,也没有显式调用超类的构造函数,那么编译器会为我们自动生成一个对超类构造函数的调用,比如:

public class ConstructorExample {

}

对于上面代码中定义的类,我们观察编译之后的字节码,我们会发现编译器为我们生成一个构造函数,如下,

aload_0

invokespecial #8; //Method java/lang/Object."":()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类,再依次对以下各类进行实例化,直到完成对目标类的实例化。具体而言,在实例化每个类时,都遵循如下顺序:先依次执行实例变量初始化和实例代码块初始化,再执行构造函数初始化。也就是说,编译器会将实例变量初始化和实例代码块初始化相关代码放到类的构造函数中去,并且这些代码会被放在对超类构造函数的调用语句之后,构造函数本身的代码之前。

java中类和实例化_Java中类的初始化与实例化相关推荐

  1. java初始化实例化_java类的初始化和实例化区别

    结论: 参考https://blog.csdn.net/qq_43672627/article/details/86616624 类的初始化:是完成程序执行前的准备工作.在这个阶段,静态的(变量,方法 ...

  2. java类放到对象_Java中类和对象总结

    一.面向对象概述 1.1 对象:世界万物皆对象. 类:同一类事物的统称,是这类对象的统称.类就是对象的设计图. 例:面向对象解决大雁南飞. 1.抽出对象--大雁 2.识别对象的属性--静态属性(翅膀. ...

  3. java类加载器顺序_java中类的加载顺序介绍(ClassLoader)

    1.ClassNotFoundExcetpion 我们在开发中,经常可以遇见java.lang.ClassNotFoundExcetpion这个异常,今天我就来总结一下这个问题.对于这个异常,它实质涉 ...

  4. java继承对象转换_java中类与对象的继承重写,存储以及自动转换和强制转换。...

    对象的继承 继承关键字:extends 继承的格式: public class 类名 extends 父类名{ } 注:一个类只能继承一个父类.子类继承父类的全部内容. 访问修饰符同类中 同包中 不同 ...

  5. java类怎么实例化_Java类的定义及其实例化

    如果你不了解类和对象的概念,请看我往期文章. 类必须先定义才能使用.类是创建对象的模板,创建对象也叫类的实例化. 下面通过一个简单的例子来理解Java中类的定义: public class Dog{ ...

  6. java 动态获取类实例化_Java:使用反射动态实例化类

    使用反射动态实例化类,可以在运行时根据参数实例化不同的类,比如使用简单的工厂模式. Factory类: public class Factory { public static Component g ...

  7. java类初始数组_java中数组初始化的三种方式是什么

    java中数组初始化的三种方式是:1.静态初始化,如[int a[] = {2, 0, 1, 9, 2020}]:2.动态初始化,如[int[] c = new int[4]]:3.默认初始化,如[i ...

  8. 什么是实例化,实例化、声明、初始化的区别

    实例化(instantiate)是指在面向对象的编程中,把用类创建对象的过程称为实例化.是将一个抽象的概念类,具体到该类实物的过程.实例化过程中一般由类名 对象名 = new 类名(参数1,参数2.. ...

  9. java类的定义的实例_Java中类的定义和初始化示例详解

    类的定义 类的定义格式 //创建类 class classname{ field ://成员属性/字段 method://方法 } class为定义类的关键字,classname为类的名字,{ }为类 ...

最新文章

  1. 钉钉扫码登录第三方_在钉钉发布公司重要文件,真的安全吗?
  2. QT的QAction类的使用
  3. k8s 手动恢复redis 集群_二进制手动部署k8s-1.14高可用集群(二、集群部署)
  4. 解决appium-inspector连接后在Appium中报错:No route found for /sessions
  5. 使用ZooKeeper ACL特性进行znode控制
  6. 苹果mac交互原型设计软件:Axure RP
  7. 如何提高阅读源代码的效率
  8. font字体的一些常用代码
  9. 关于HTML按钮跳转方法(及其相关)
  10. 驱动精灵w8ndows xp sp2,爱普生Epson TM-T90打印机驱动官方正式版下载,适用于winxp,winvista,win7,win8,win10-驱动精灵...
  11. 常用1寸,2寸照片标准尺寸
  12. 百旺开票清单导入模板_半年来最爱的高效率工具滴答清单不为人知的使用技巧...
  13. 生成费氏数列 -思维训练for
  14. 个人作业——关于K米的产品案例分析
  15. JS一些常用证件信息的正则表达式
  16. css border:solid实线,dashed虚线;dotted点状,,double双线
  17. 蘑菇街App的组件化之路·续
  18. L3-015 球队“食物链” (30 分)
  19. 企业如何做新闻软文发布? 软文推广和新闻源发布有何不同之处?
  20. MySQL的基本用法

热门文章

  1. 震惊!原来leetcode竟然真的能中奖?
  2. 深度融合 | 当推荐系统遇见知识图谱(三)
  3. 【论文复现】使用RNN进行文本分类
  4. python支付宝自动支付_python-支付宝支付示例
  5. dmp导入数据 oracle_一文看懂oracle12c数据库跨小版本迁移
  6. mysql 二进制 nodejs_nodejs怎么存取2进制数据到数据库?
  7. java motherfree video_Java Config 下的Spring Test方式
  8. 吴恩达机器学习 12.异常检测
  9. Java并发(二)——ThreadLocal
  10. 花书+吴恩达深度学习(七)优化方法之基本算法(Momentum, Nesterov, AdaGrad, RMSProp, Adam)