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中并不是单纯的使用某一种算法进行垃圾回收,而是将不同的垃圾回收算法包装在不同的垃圾回收器当中, ...
随机推荐
- vue 实现滑块验证码
图一为拖拽前效果,图二为拖拽后效果 一.新建文件JcRange.vue,代码如下: 1.模板代码: <template> <div class="jc-component_ ...
- Mysql UDF提权方法
0x01 UDF UDF(user defined function)用户自定义函数,是mysql的一个拓展接口.用户可以通过自定义函数实现在mysql中无法方便实现的功能,其添加的新函数都可以在sq ...
- Redis 分布式锁(一)
前言 本文力争以最简单的语言,以博主自己对分布式锁的理解,按照自己的语言来描述分布式锁的概念.作用.原理.实现.如有错误,还请各位大佬海涵,恳请指正.分布式锁分两篇来讲解,本篇讲解客户端,下一篇讲解r ...
- 第三章:View的事件体系
3.1 View的基础知识 主要有:View的位置参数,MotionEvent和TouchSlop对象,VelocityTracker,GestureDetector和Scroller对象 3.1.1 ...
- 使用recv函数和send函数的技巧
一些述说 recv函数用于socket通信中接收消息,接口定义如下: int recv(SOCKET s, char *buf, int len, int flags) 参数一:指定接收端套接字描述符 ...
- .Net、ASP.Net、C#、VisualStudio之间的关系是什么
.Net一般指的是.NetFramework,提供了基础的.Net类,这些类可以被任何一种.Net编程语言调 用,.NetFramework还提供了 CLR.JIT.GC等基础功能. ASP.Net是 ...
- django-rest-framework-源码解析001-整体框架
简介 Django Rest Framework是一个强大且灵活的工具包,主要用以构建RESTful风格的Web API. Django REST Framework(简称DRF)可以在Django的 ...
- grpc 之 word2pdf使用
做一个word转pdf的服务,采用grpc,使用libreoffice命令. 1.构建libreoffice镜像 FROM python:3.6 ENV TZ=Asia/Shanghai RUN ...
- 数据库事务与MySQL事务总结
事务特点:ACID 从业务角度出发,对数据库的一组操作要求保持4个特征: Atomicity(原子性):一个事务必须被视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败 ...
- git pull & git fetch
Git中从远程的分支获取最新的版本到本地有这样2个命令:1. git fetch:相当于是从远程获取最新版本到本地,不会自动merge git fetch origin mastergit log ...