摘要:很多时候提到类加载,大家总是没法马上回忆起程序,这篇文章会用一个例子为你把类加载的诸多问题一次性廓清。

本文分享自华为云社区《用1个例子加5个问题,一次性搞清java中的类加载问题【奔跑吧!JAVA】》,原文作者:breakDraw 。

很多时候提到类加载,大家总是没法马上回忆起程序,这篇文章会用一个例子为你把类加载的诸多问题一次性廓清。

Java类的加载程序

援用1个网上的经典例子,并做稍许改变,以便大家更好地了解。

原例子援用自:https://blog.csdn.net/zfx2013...

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:

  1. 成员显示赋值、非动态代码块(按定义程序)
  2. 构造方法
    因而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。

总结一下程序:

  1. 父类动态变量显式赋值、父类动态代码块(按定义程序)
  2. 子类动态变量显式赋值、子类动态代码块(按定义程序)
  3. 父类非动态变量显式赋值(父类实例成员变量)、父类非动态代码块(按定义程序)
  4. 父类构造函数
  5. 子类非动态变量(子类实例成员变量)、子类非动态代码块(按定义程序)
  6. 子类构造函数。

类加载过程

Q:类加载的3个必经阶段是:
A:

  1. 加载(类加载器读取二进制字节流,生成java类对象)
  2. 链接(验证,调配动态域初始零值)
  3. 初始化(后面的题目讲的其实就是初始化时的程序)
    更具体的如下:

被动援用中和类动态初始化的关系

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个不同的加载器加载进去雷同的类或接口时, 实际上是不同的。

点击关注,第一工夫理解华为云陈腐技术~