一、概述

我们知道,一个对象在可以被使用之前必须要被正确地实例化。而实例化实际指的就是以一个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. Unity3D Demo项目开发记录

    前言 经过一段时间的学习与实际开发,unity3D也勉强算是强行入门了,正所谓好记性不如烂笔头,更何况本人并非专业从事unity3D开发,会一点C#但也并不熟悉,为了避免后期遗忘,因此特意整理了一个D ...

  2. Ethical Hacking - NETWORK PENETRATION TESTING(1)

    Pre--Connection-Attacks that can be done before connecting to the network. Gaining Access - How to b ...

  3. less : 解决升级后报错的问题

    vue2项目. 上版本. { "name": "xxx", "version": "1.0.0", "desc ...

  4. p41_数据报与虚电路

    一.定义 数据报方式为网络层提供无连接服务. 无连接服务:不事先为分组的传输确定传输路径,每个分组独立确定传输路径,不同分组传输路径可能不同. 虚电路方式为网络层提供连接服务 连接服务:首先为分组的传 ...

  5. pandas | 如何在DataFrame中通过索引高效获取数据?

    本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是pandas数据处理专题的第四篇文章,我们一起来聊聊DataFrame中的索引. 上一篇文章当中我们介绍了DataFrame数据结构当 ...

  6. python多线程之Threading

    什么是线程? 线程是操作系统内核调度的基本单位,一个进程中包含一个或多个线程,同一个进程内的多个线程资源共享,线程相比进程是“轻”量级的任务,内核进行调度时效率更高. 多线程有什么优势? 多线程可以实 ...

  7. 一张PDF了解JDK11 GC调优秘籍-附PDF下载

    目录 简介 废弃的VM选项 Source-File Mode Code Heap状态分析 AppCDS 总结 简介 JDK11相比JDK10,添加了一个新的Source-File Mode,可以直接通 ...

  8. CODING DevOps + Nginx-ingress 实现自动化灰度发布

    作者:王炜,CODING DevOps 后端开发工程师,拥有多年研发经验,云原生.DevOps.Kubernetes 资深爱好者,Servicemesher 服务网格中文社区成员.获得 Kuberne ...

  9. BUUCTF-Web Easy Calc

    要素察觉 打开calc.php发现源码 过滤了很多字符.题目一开始提示了有waf,最后通过eval实现计算功能.考虑利用该函数读取flag文件,先尝试弹个phpinfo 被waf拦截,在num参数前面 ...

  10. java基础(11)--封装

    一.java面向对象三大特别: 1.封装 2.继承 3.多态   二.封装的作用 1.属性私有化(private) 2.对外提供简单的入口 如公开的set()与get()方法,并且都不带static ...