JVM初探(五):类的实例化
一、概述
我们知道,一个对象在可以被使用之前必须要被正确地实例化。而实例化实际指的就是以一个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初探(五):类的实例化的更多相关文章
- jvm系列 (五) ---类的加载机制
类的加载机制 目录 jvm系列(一):jvm内存区域与溢出 jvm系列(二):垃圾收集器与内存分配策略 jvm系列(三):锁的优化 jvm系列 (四) ---强.软.弱.虚引用 我的博客目录 什么是类 ...
- 深入分析Java反射(五)-类实例化和类加载
前提 其实在前面写过的<深入分析Java反射(一)-核心类库和方法>已经介绍过通过类名或者java.lang.Class实例去实例化一个对象,在<浅析Java中的资源加载>中也 ...
- JVM初探(三):类加载机制
一.概述 我们知道java代码会被编译为.class文件,这里class文件中的类信息最终还是需要jvm加载以后才能使用. 事实上,虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验,转 ...
- 【死磕JVM】五年 整整五年了 该知道JVM加载机制了!
类加载 Java虚拟机类加载过程是把Class类文件加载到内存,并对Class文件中的数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的java类型的过程 和那些编译时需要连接工作的语言不 ...
- JVM加载类的过程,双亲委派机制中的方法
JVM加载类的过程: 1)JVM中类的整个生命周期: 加载=>验证=>准备=>解析=>初始化=>使用=>卸载 1.1.加载 类的加载阶段,主要是获取定义此类的二进 ...
- JVM初探 -JVM内存模型
JVM初探 -JVM内存模型 标签 : JVM JVM是每个Java开发每天都会接触到的东西, 其相关知识也应该是每个人都要深入了解的. 但接触了很多人发现: 或了解片面或知识体系陈旧. 因此最近抽时 ...
- JVM系列五:JVM监测&工具
JVM系列五:JVM监测&工具[整理中] http://www.cnblogs.com/redcreen/archive/2011/05/09/2040977.html 前几篇篇文章介绍了介 ...
- 【java】java反射初探 ——“当类也照起镜子”
反射的作用 开门见山地说说反射的作用 1.为我们提供了全面的分析类信息的能力 2.动态加载类 我理解的“反射”的意义 (仅个人理解) 我理解的java反射机制就是: 提供一套完善而强大的API ...
- 深入理解JVM(五)——垃圾回收器
轻松学习JVM(五)——垃圾回收器 上一篇我们介绍了常见的垃圾回收算法,不同的算法各有各的优缺点,在JVM中并不是单纯的使用某一种算法进行垃圾回收,而是将不同的垃圾回收算法包装在不同的垃圾回收器当中, ...
随机推荐
- JavaScript 基础 学习 (四)
JavaScript 基础 学习 (四) 解绑事件 dom级 事件解绑 元素.on事件类型 = null 因为赋值的关系,所以给事件赋值为 null 的时候 事件触发的时候,就没有事件处理 ...
- day8 python 列表,元组,集合,字典的操作及方法 和 深浅拷贝
2.2 list的方法 # 增 list.append() # 追加 list.insert() # 指定索引前增加 list.extend() # 迭代追加(可迭代对象,打散追加) # 删 list ...
- Web应用程序安全与风险
一.Web应用程序安全与风险 更多渗透测试相关内容请关注此地址:https://blog.csdn.net/weixin_45380284 1.web发展历程 静态内容阶段(HTML) CGI程序阶段 ...
- PHP常见的十个安全问题
相对于其他几种语言来说, PHP 在 web 建站方面有更大的优势,即使是新手,也能很容易搭建一个网站出来.但这种优势也容易带来一些负面影响,因为很多的 PHP 教程没有涉及到安全方面的知识. 此帖子 ...
- static关键字和final关键字
static关键字和final关键字 static(静态) 作用 用来修饰属性.方法.代码块.内部类 static修饰属性 表示静态变量(类变量) 按是否使用static修饰,属性的分类 静态属性 当 ...
- Oracle对表进行备份
前言: 在实际开发中,我们常常需要对单张或多张表进行备份,以下博主就从这两个方面进行总结.如需转载,请标明来处,谢谢! 在备份前我们先创建表盒相关测试的数据 -- Create table creat ...
- 带你理解Lock锁原理
同样是锁,先说说synchronized和lock的区别: synchronized是java关键字,是用c++实现的:而lock是用java类,用java可以实现 synchronized可以锁住代 ...
- Spring Security 实战干货:图解用户是如何登录的
1. 前言 欢迎阅读Spring Security 实战干货系列文章,在集成Spring Security安全框架的时候我们最先处理的可能就是根据我们项目的实际需要来定制注册登录了,尤其是Http登录 ...
- Andriod开发---《横竖屏切换时 Activity的生命周期的总结》
横屏切换竖屏Activity的生命周期详解,下面分析一下切换时具体的生命周期: 1.新建一个Activity,并把各个生命周期打印出来 2.运行Activity,得到如下信息 onCreate--&g ...
- 一步步教你用Prometheus搭建实时监控系统系列(二)——详细分析拉取和推送两种不同模式
前言 本系列着重介绍Prometheus以及如何用它和其周边的生态来搭建一套属于自己的实时监控告警平台. 本系列受众对象为初次接触Prometheus的用户,大神勿喷,偏重于操作和实战,但是重要的概念 ...