一次性搞清Java中的类加载问题
摘要:很多时候提到类加载,大家总是没法马上回忆起顺序,这篇文章会用一个例子为你把类加载的诸多问题一次性澄清。
本文分享自华为云社区《用1个例子加5个问题,一次性搞清java中的类加载问题【奔跑吧!JAVA】》,原文作者:breakDraw 。
很多时候提到类加载,大家总是没法马上回忆起顺序,这篇文章会用一个例子为你把类加载的诸多问题一次性澄清。
Java类的加载顺序
引用1个网上的经典例子,并做稍许改动,以便大家更好地理解。
原例子引用自:https://blog.csdn.net/zfx2013/article/details/89453482
public class Animal {
private int i = test();
private static int j = method();
static {
System.out.println("a");
}
Animal(){
System.out.println("b");
}
{
System.out.println("c");
}
public int test(){
System.out.println("d");
return 1;
}
public static int method(){
System.out.println("e");
return 1;
}
}
public class Dog extends Animal{
{
System.out.println("h");
}
private int i = test();
static {
System.out.println("f");
}
private static int j = method(); Dog(){
System.out.println("g");
}
public int test(){
System.out.println("i");
return 1;
}
public static int method(){
System.out.println("j");
return 1;
}
public static void main(String[] args) {
Dog dog = new Dog();
System.out.println();
Dog dog1 = new Dog();
}
}
执行这段main程序,会输出什么?
答案是
eafjicbhig
icbhig
为了方便大家一个个细节去理解, 我换一种方式去提问。
Q: 什么时候会进行静态变量的赋值和静态代码块的执行?
A:
- 第一次创建某个类或者某个类的子类的实例
- 访问类的静态变量、调用类的静态方法
- 使用反射方法forName
- 调用主类的main方法(本例子的第一次静态初始化其实属于这个情况,调用了Dog的main方法)
注: 类初始化只会进行一次, 上面任何一种情况触发后,之后都不会再引起类初始化操作。
Q:初始化某个子类时,也会对父类做静态初始化吗?顺序呢?
A:如果父类之前没有被静态初始化过,那就会进行, 且顺序是先父类再子类。 后面的非静态成员初始化也是如此。
所以会先输出eafj。
Q: 为什么父类的method不会被子类的method重写?
A: 静态方法是类方法,不会被子类重写。毕竟类方法调用时,是必定带上类名的。
Q: 为什么第一个输出的是e而不是a?
A: 因为类变量的显示赋值代码和静态代码块代码按照从上到下的顺序执行。
Animal的静态初始化过程中,method的调用在static代码块之前,所以先输出e再输出a。
而Dog的静态初始化过程中,method的调用在static代码块之后,因此先输出f,再输出j
Q: 没有在子类的构造器中调用super()时,也会进行父类对象的实例化吗?
A: 会的。会自动调用父类的默认构造器。 super()主要是用于需要调用父类的特殊构造器的情况。
因此会先进行Animal的对象实例化,再进行Dog的对象实例化
Q: 构造方法、成员显示赋值、非静态代码块(即输出c和h的那2句)的顺序是什么?
A:
- 成员显示赋值、非静态代码块(按定义顺序)
- 构造方法
因此Animal的实例化过程输出icb(如果对输出i有疑问,见下面一题)
接着进行Dog的实例化,输出hig
Q: 为什么Animal实例化时, i=test()中输出的是i而不是d?
A:因为你真正创建的是Dog子类,Dog子类中的test()方法由于签名和父类test方法一致,因此test方法被重写了。
此时即使在父类中调用,也还是用使用子类Dog的方法。除非你new的是Animal。
Q: 同上题, 如果test方法都是private或者final属性, 那么上题的情况会有变化吗??
A:
因为private和final方法是不能被子类重写的。
所以Animal实例化时,i=test输出d。
总结一下顺序:
- 父类静态变量显式赋值、父类静态代码块(按定义顺序)
- 子类静态变量显式赋值、子类静态代码块(按定义顺序)
- 父类非静态变量显式赋值(父类实例成员变量)、父类非静态代码块(按定义顺序)
- 父类构造函数
- 子类非静态变量(子类实例成员变量)、子类非静态代码块(按定义顺序)
- 子类构造函数。
类加载过程
Q:类加载的3个必经阶段是:
A:
- 加载(类加载器读取二进制字节流,生成java类对象)
- 链接(验证,分配静态域初始零值)
- 初始化(前面的题目讲的其实就是初始化时的顺序)
更详细的如下:
被动引用中和类静态初始化的关系
Q:new某个类的数组时,会引发类初始化吗?
像下面输出什么
public class Test {
static class A{
public static int a = 1;
static{
System.out.println("initA");
}
} public static void main(String[] args) {
A[] as = new A[5];
}
}
A:
new数组时,不会引发类初始化。
什么都不输出。
Q:引用类的final静态字段,会引发类初始化吗?
像下面输出什么?
public class Test {
static class A{
public static final int a = 1;
static{
System.out.println("initA");
}
} public static void main(String[] args) {
System.out.println("A.a=" + A.a);
}
}
A: 不会引发。
不会输出initA。 去掉final就会引发了。
(注意这里必须是基本类型常量, 如果是引用类型产量,则会引发类初始化)
Q:子类引用了父类的静态成员,此时子类会做类初始化嘛?
如下会输出什么
public class Test {
static class A{
public static int a = 1;
static{
System.out.println("initA");
}
} static class B extends A{
static {
System.out.println("initB");
}
} public static void main(String[] args) {
System.out.println("B.a=" + B.a);
}
}
A:
子类不会初始化。
打印initA,却不会打印initB。
类加载器
双亲委派
类加载时的双亲委派模型,不知道能怎么出题。。。反正就记得优先去父类加载器中看类是否能加载。
就贴个图吧:
注意,上面的图有问题。
Bootsrap不是ClassLoader的子类,他是C++编写的。
而ExtClassLoader和AppClassLoader都是继承自ClassLoader的
Q:java中, 是否类和接口的包名和名字相同, 那么就一定是同一个类或者接口?
A:错误。
1个jvm中, 类和接口的唯一性由 二进制名称以及它的定义类加载器 共同决定。
因此2个不同的加载器加载出来相同的类或接口时, 实际上是不同的。
一次性搞清Java中的类加载问题的更多相关文章
- Java中的类加载器
转载:http://blog.csdn.net/zhangjg_blog/article/details/16102131 从java的动态性到类加载机制 我们知道,Java是一种动态语言.那么怎 ...
- Java中的类加载器以及Tomcat的类加载机制
在加载阶段,虚拟机需要完成以下三件事情: 1.通过一个类的全限定名来获取其定义的二进制字节流. 2.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构. 3.在Java堆中生成一个代表这个类 ...
- Java中的类加载器--Class loader
学习一下Java中的类加载器,这个是比较底层的东西,好好学习.理解一下. 一.类加载器的介绍 1.类加载器:就是加载类的工具,在java程序中用到一个类,java虚拟机首先要把这个类的字节码加载到内 ...
- [读书笔记]java中的类加载器
以下内容大多来自周志明的<深入理解Java虚拟机>. 类加载器是java的一项创新,也是java流行的重要原因之一,它最初是为了满足java applet的需求而开发出来. 什么是appl ...
- java中的类加载器ClassLoader和类初始化
每个类编译后产生一个Class对象,存储在.class文件中,JVM使用类加载器(Class Loader)来加载类的字节码文件(.class),类加载器实质上是一条类加载器链,一般的,我们只会用到一 ...
- 关于java中的类加载器
什么是类加载器? 类加载器是专门负责加载类的命令或者说工具 ClassLoader java中的3个类加载器 JDK中自带了3个类加载器 启动类加载器 扩展类加载器 应用类加载器 假设有这样一段代码 ...
- Java中的类加载器----ClassLoader
1.简单的讲类加载器就是加载类. 在一个类要被执行时,首先会被从硬盘中加载到内存中,这个任务就是由类加载器来完成,如果加载不成功时,类是无法被执行的.类加载器执行的都是字节码二进制文件. 帮助文档 ...
- java中获取类加载路径和项目根路径的5种方法
import java.io.File; import java.io.IOException; import java.net.URL; public class MyUrlDemo { publi ...
- java中获取类加载路径和项目根路径的5种方式分析
package my; import Java.io.File; import java.io.IOException; import java.net.URL; public class MyUrl ...
随机推荐
- windows 7系统封装总结
win7系统封装总结 需求:对于个人家庭用户,网上下载原版镜像或者下载好别人封装好的系统都无所谓,但是在公司办公的特殊环境下, 有时需要经常装一些特殊的软件,根据实际情况,封装一个适合本公司使用环境的 ...
- easy-flows源码研习
一.项目概述 1.原项目github地址:https://github.com/j-easy/easy-flows.git 2.easy-flows可以用非常简易的api创建常用的可组合的工作流.涵盖 ...
- 折腾Linux内核编译
计网提高实验.指导书给的是远古版本2.6.39.2,轻易在某hub上找到下载地址 查表 看起来Ubuntu 11.04离得最近,遂下一个镜像 懒得上google检索了,编译准备先follow这篇博客试 ...
- Promise解析(待完成)
Promise是一种异步操作的解决方案,将写法复杂的传统的回调函数和监听事件的异步操作,用同步代码的形式表达出来.避免了多级异步操作的回调函数嵌套. 1.主要用于异步计算 2.可以将异步操作队列化,按 ...
- jquery的入口函数 和 js和jq的转化
先引入jq包 然后<script type="text/javascript"> $(function{ 获取标签:$('#box2') jq转为js:$('#box2 ...
- ruby基础(一)
Ruby基础 1.对象.变量和常量 1.1 对象 在Ruby中表示数据的最基本单位是对象,任何数据都是对象,使用类来表示对象的种类. 一个某个类的对象称作对象的实例. 对象 类 eg 数值 Numer ...
- 【转载】基于RedHatEnterpriseLinux V7(RHEL7)下SPEC CPU 2006环境搭建以及测试流程(之一)——介绍、安装准备、安装、config文件以及运行脚本介绍
基于RedHatEnterpriseLinux V7(RHEL7)下SPEC CPU 2006环境搭建以及测试流程(之一)--介绍.安装准备.安装.config文件以及运行脚本介绍 其他 2018-0 ...
- 1.4 重置root用户密码
图1-45 系统的欢迎界面 1.4 重置root用户密码 平日里让运维人员头疼的事情已经很多了,因此偶尔把Linux系统的密码忘记了并不用慌,只需简单几步就可以完成密码的重置工作.但是,如果您是第一 ...
- ssh远程主机执行命令或脚本
1.执行单一命令 [root@vps ~]# ssh user@192.168.9.243 "pwd; ls; rm -f Cent* ;echo --------; ls"/ho ...
- MySQL之数据操纵语言(DML)
数据操纵语言(DML) 数据操纵语(Data Manipulation Language),简称DML. DML主要有四个常用功能. 增 删 改 查 insert delete update sele ...