一、概述

我们知道,一个对象在可以被使用之前必须要被正确地实例化。而实例化实际指的就是以一个java类为模板创建对象/实例的过程。比如说常见的 Person = new Person()代码就是一个将Person类实例化并创建引用的过程。

对于类的实例化,我们关注两个问题:

  • 如何实例化?(类的四种实例化方式)
  • 什么时候实例化?(类的一个初始化过程和对象的三个初始化过程)

二、类的四种实例化方式

1.使用new关键字

这也是最常见最简单的创建对象的方法。通过这种方法,我们可以借助类的构造函数实例化对象。

Parent p = new Parent();

2.使用newInstance()方法

我们可以先通过类的全限定名获取类,然后通过Class类的newInstance()方法去调用类的无参构造方法创建一个对象:

Class p = Class.forName("com.huang.Parent");
Parent parent = (Parent) p.newInstance();

或者通过java.lang.relect.Constructor类里的newInstance()方法去构造对象,这个方法比起Class自带的更强大:

它可以调用类中有参构造方法私有构造方法创建对象!

//Parent私有的含参构造方法
public Parent(int a) {
System.out.println("Parent创建了!");
} //通过Constructor调用
Class p = Class.forName("com.huang.Parent");
Constructor<Parent> parentConstructor = p.getConstructor(int.class);
Parent parent = (Parent) p.newInstance();

3.使用clone()方法

当我们调用clone方法,JVM会帮我们创建一个新的、一样的对象,特别需要说明的是,用clone方法创建对象的过程中并不会调用任何构造函数。这里涉及到一个深拷贝和浅拷贝的知识点,我会另起一篇随笔介绍,这里就多费笔墨了。

Parent parent = new Parent();
Parent p2 = (Parent) parent.clone();

4.使用反序列化机制

当我们反序列化一个对象时,JVM会给我们创建一个单独的对象,在此过程中,JVM并不会调用任何构造函数。

Parent parent = new Parent();

// 写对象
ObjectOutputStream outputStream = new ObjectOutputStream(
new FileOutputStream("parent.bin"));
outputStream.writeObject(parent);
outputStream.close(); // 读对象
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(
"parent.bin"));
Parent parent2 = (Parent) inputStream.readObject();

三、类实例化的过程

我们以 Person p = new Person()这条语句为例,当这条语句执行的时候,jvm进行了四步操作:

  • 先执行new语句,以Person类为模板,在堆中创建Person对象
  • 为Person对象执行构造方法(如果有父类会先执行父类构造方法)
  • 创建Person类的引用变量p
  • 将引用变量p指向内存中Person对象

我们不难看出,其实实例化的过程其实就是第一和第二步,在这两步里,jvm其实也进行了四步操作:

  • Person的初始化
  • Person对象变量的初始化(如果有父类会先执行父类变量的初始化)
  • Person对象代码块的初始化
  • Person对象构造函数的初始化(如果有父类会先执行父类初始化)

1.类的初始化

对于第一次被实例化的对象,第一步是必定是类的初始化,所以静态变量和静态代码块中的代码必然被赋值和执行。

这点在我关于类加载机制的文章中已有解释,这里就不多费笔墨。

2.对象变量的初始化

我们在定义对象中的变量的同时,还可以直接对对象变量进行赋值。它们会在构造函数执行之前完成这些初始化操作

//父类
public class Parent{
int i = 1;
int j = i + 1; public Parent() {
System.out.println("Parent的构造方法执行了!");
j += 10;
}
} //子类
public class Child extends Parent { int k = 1;
int l = k + 1; public Child() {
System.out.println("i:"+i);
System.out.println("j:"+j);
System.out.println("k:"+k);
System.out.println("l:"+l);
System.out.println("Child的构造方法执行了!");
k += 8;
System.out.println("k:"+k);
System.out.println("l:"+l);
}
} public static void main( String[] args ) {
Child child = new Child();
}
//执行结果
Parent的构造方法执行了!
i:1
j:12
k:1
l:2
Child的构造方法执行了!
k:9
l:2

我们可以知道执行顺序是这样的:

  • 父类的变量初始化i = 1,j=2;
  • 执行父类的构造函数j = 2 + 10 = 12;
  • 子类的变量初始化k = 1,l = 2;
  • 执行子类构造函数k = 1 + 8 = 9

这里有人认为父类的变量初始化了,而且父类的构造函数也执行了,那父类是不是也一起实例化了?

答案是没有,我们可以认为实例化的时候子类从父类一起拷贝了一份变量,构造函数的执行也是为了能让父类的变量初始化,最后实例化放到内存里的其实是子类+父类的一个混合体!

3.代码块的初始化

我们一般指的代码块是构造代码块和静态代码块,静态代码块在类初始化时就执行,而构造代码块在类一创建就执行,也优先于构造方法

我们举个例子:

//父类
public class Parent{
{
System.out.println("Child的代码块被执行了!");
}
public Parent() {
System.out.println("Parent创建了!");
}
} //子类
public class Child extends Parent { public Child() {
System.out.println("Child创建了!");
} static {
System.out.println("Child的构造方法执行了!");
} {
System.out.println("Child的代码块被执行了!");
}
} //执行代码
public static void main( String[] args ) {
Child child = new Child();
} //打印结果
Parent的代码块被执行了!
Parent的构造方法执行了!
Child的代码块被执行了!
Child的构造方法执行了!

我们可以知道执行顺序是这样的:

  • 父类代码块
  • 父类的构造方法
  • 子类的代码块
  • 子类的构造方法

4.构造函数的初始化

我们可以从上文知道,实例变量初始化与实例代码块初始化总是发生在构造函数初始化之前,那么我们下面着重看看构造函数初始化过程。众所周知,每一个Java中的对象都至少会有一个构造函数,如果我们没有显式定义构造函数,那么它将会有一个默认无参的构造函数。在编译生成的字节码中,这些构造函数会被命名成<init>()方法。

事实上,Java强制要求Object对象之外的所有对象构造函数的第一条语句必须是父类构造函数的调用语句,如果没有就会默认生成讴歌构造函数。这就保证了不管要实例化的类继承了多少父类,我们最终都能让实例继承到所有从父类继承到的属性。

5.小结

结合以上文,我们可以看出类的实例化其实是一个递归的过程。

从子类不断向上递归,然后一直递归到直到抵达基类Object,然后一层一层的返回,先完成类的初始化:

  • 如果有类未初始化就先初始化(初始化静态块)

再回到Object类,往下一层一层的返回,完成对象的三个初始化:

  • 初始化变量
  • 初始化代码块
  • 初始化构造函数

所以最终我们可以总结出类初始化过程中类的各种代码块的执行顺序:

  • 父类静态块
  • 子类静态块
  • 父类代码块
  • 父类构造函数
  • 子类代码块
  • 子类构造函数

验证一下:

//父类
public class Parent{
static {
System.out.println("Parent的静态块执行了!");
} public Parent() {
System.out.println("Parent的构造方法执行了!");
} {
System.out.println("Parent的代码块被执行了!");
}
} //子类
public class Child extends Parent {
static {
System.out.println("Child的静态块执行了!");
} public Child() {
System.out.println("Child的构造方法执行了!");
} {
System.out.println("Child的代码块被执行了!");
}
} public static void main( String[] args ) {
Child child = new Child();
System.out.println();
} //输出结果
Parent的静态块执行了!
Child的静态块执行了!
Parent的代码块被执行了!
Parent的构造方法执行了!
Child的代码块被执行了!
Child的构造方法执行了!

JVM初探(五):类的实例化的更多相关文章

  1. jvm系列 (五) ---类的加载机制

    类的加载机制 目录 jvm系列(一):jvm内存区域与溢出 jvm系列(二):垃圾收集器与内存分配策略 jvm系列(三):锁的优化 jvm系列 (四) ---强.软.弱.虚引用 我的博客目录 什么是类 ...

  2. 深入分析Java反射(五)-类实例化和类加载

    前提 其实在前面写过的<深入分析Java反射(一)-核心类库和方法>已经介绍过通过类名或者java.lang.Class实例去实例化一个对象,在<浅析Java中的资源加载>中也 ...

  3. JVM初探(三):类加载机制

    一.概述 我们知道java代码会被编译为.class文件,这里class文件中的类信息最终还是需要jvm加载以后才能使用. 事实上,虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验,转 ...

  4. 【死磕JVM】五年 整整五年了 该知道JVM加载机制了!

    类加载 Java虚拟机类加载过程是把Class类文件加载到内存,并对Class文件中的数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的java类型的过程 和那些编译时需要连接工作的语言不 ...

  5. JVM加载类的过程,双亲委派机制中的方法

    JVM加载类的过程: 1)JVM中类的整个生命周期: 加载=>验证=>准备=>解析=>初始化=>使用=>卸载  1.1.加载 类的加载阶段,主要是获取定义此类的二进 ...

  6. JVM初探 -JVM内存模型

    JVM初探 -JVM内存模型 标签 : JVM JVM是每个Java开发每天都会接触到的东西, 其相关知识也应该是每个人都要深入了解的. 但接触了很多人发现: 或了解片面或知识体系陈旧. 因此最近抽时 ...

  7. JVM系列五:JVM监测&工具

    JVM系列五:JVM监测&工具[整理中]  http://www.cnblogs.com/redcreen/archive/2011/05/09/2040977.html 前几篇篇文章介绍了介 ...

  8. 【java】java反射初探 ——“当类也照起镜子”

    反射的作用   开门见山地说说反射的作用 1.为我们提供了全面的分析类信息的能力 2.动态加载类   我理解的“反射”的意义 (仅个人理解) 我理解的java反射机制就是: 提供一套完善而强大的API ...

  9. 深入理解JVM(五)——垃圾回收器

    轻松学习JVM(五)——垃圾回收器 上一篇我们介绍了常见的垃圾回收算法,不同的算法各有各的优缺点,在JVM中并不是单纯的使用某一种算法进行垃圾回收,而是将不同的垃圾回收算法包装在不同的垃圾回收器当中, ...

随机推荐

  1. Ethical Hacking - Overview

    Hacking is gaining unauthorized access to anything. Preparation Setting up a lab and installing need ...

  2. C#数据结构与算法系列(二十三):归并排序算法(MergeSort)

    1.介绍 归并排序(MergeSort)是利用归并的思想实现的排序方法,该算法采用经典的分治策略(分治法将问题分(divide)成一些小的问题然后递归求解, 而治(conquer)的阶段则将分的阶段得 ...

  3. 「美团面试系列」面试加分项,这样说你会JVM,面试官还能问什么

    Java性能调优都是老生常谈的问题,特别当“糙快猛”的开发模式大行其道时,随着系统访问量的增加.代码的臃肿,各种性能问题便会层出不穷. 比如,下面这些典型的性能问题,你肯定或多或少都遇到过: 在进行性 ...

  4. 【JVM之内存与垃圾回收篇】堆

    堆 堆的核心概念 堆针对一个 JVM 进程来说是唯一的,也就是一个进程只有一个 JVM,但是进程包含多个线程,他们是共享同一堆空间的. 一个 JVM 实例只存在一个堆内存,堆也是 Java 内存管理的 ...

  5. python基础--深浅copy(重点)

    在此申明一下,博客参照了https://www.cnblogs.com/jin-xin/,自己做了部分的改动 深浅copy(重点) 先问问大家,什么是拷贝?拷贝是音译的词,其实他是从copy这个英文单 ...

  6. MySQL索引介绍和实战

    索引是什么 MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构. 可以得到索引的本质:索引是数据结构,索引的目的是提高查询效率,可以类比英语新华字典,根据目录定位词 ...

  7. 查看 __class__属性

    查看complex的__class__属性 a = 5+2j print(a.__class__) print(a.__class__.__class__) ''' <class 'comple ...

  8. PHP prev() 函数

    实例 输出数组中的当前元素.下一个元素和上一个元素的值: <?php$people = array("Peter", "Joe", "Glenn ...

  9. PHP fclose() 函数

    定义和用法 fclose() 函数关闭打开的文件. 该函数如果成功则返回 TRUE,如果失败则返回 FALSE. 语法 fclose(file) 参数 描述 file 必需.规定要关闭的文件. 实例 ...

  10. PHP fread() 函数

    定义和用法 fread() 函数读取打开的文件. 函数会在到达指定长度或读到文件末尾(EOF)时(以先到者为准),停止运行. 该函数返回读取的字符串,如果失败则返回 FALSE. 语法 string ...