关于java:一次性搞清Java中的类加载问题

3次阅读

共计 3116 个字符,预计需要花费 8 分钟才能阅读完成。

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

本文分享自华为云社区《用 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 个不同的加载器加载进去雷同的类或接口时,实际上是不同的。

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

正文完
 0