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中并不是单纯的使用某一种算法进行垃圾回收,而是将不同的垃圾回收算法包装在不同的垃圾回收器当中, ...
随机推荐
- Cmd重定向
1.执行单条cmd命令 public static string ExecuteCmd(string command) { Process p = new Process(); p.StartInfo ...
- ffmpeg常见用法总结
1. 视频/音频剪切: ffmpeg -i input_file [-ss 00:00:10] [-t 00:00:20] output_file 去掉-ss指令表示从头开始 去掉-t指令表示剪切到结 ...
- Ethical Hacking - GAINING ACCESS(22)
CLIENT SIDE ATTACKS - BeEf Framework Browser Exploitation Framework allowing us to launch a number o ...
- threadLocal源码土话解说
前言 废话不多说,先了解什么是threadLocal,下面是threadLocal类的说明注释, 这段话大致(猜的)意思是,改类为线程提供了一个局部变量,但是呢,这个变量和普通的变量又有所不同,怎么不 ...
- Apache 阿帕奇 配置运行环境
阿帕奇 Apache 是一个很常用的服务器环境. 安装阿帕奇后,需要对配置文件进行修改,才能使用. https.conf是阿帕奇的配置文件,地址在 阿帕奇的安装目录\conf\httpd.conf 默 ...
- python Scrapy 从零开始学习笔记(一)
在之前我做了一个系列的关于 python 爬虫的文章,传送门:https://www.cnblogs.com/weijiutao/p/10735455.html,并写了几个爬取相关网站并提取有效信息的 ...
- java排序方式对比
尽量使用使用Comparator进行排序, 在java中,要想给数据进行排序,有两种事项方式, 一种为实现Comparable接口, 一种是实现Comparator接口, public interfa ...
- three.js 数学方法之Matrix4
今天郭先生说一说three.js中的Matrix4,相较于Matrix3来说,Matrix4和three.js联系的更紧密,因为在4x4矩阵最常用的用法是作为一个变换矩阵.这使得表示三维空间中的一个点 ...
- SpringBoot集成Dubbo+Zookeeper
目录 Spring版本 dubbo_zookeeper负责定义接口 dubbo_provider 服务提供者 dubbo_consumer服务使用者 Spring版本 不知道为啥,新创建的Spring ...
- ken桑带你读源码 之scrapy scrapy\core\scheduler.py
从英文来看是调度程序 我们看看是怎么调度 首先爬虫队列有两个 一个是保存在内存中 没有历史记录 重新开始 42行 self.mqs = self.pqclass(self._newmq) ...