• 2.通过demo分析类加载过程及结论


    1. 静态变量所在类加载过程

    
    
    /**
     * @author ztkj-hzb
     * @Date 2019/11/1 11:46
     * @Description
     */
    public class Test1 {
    
        public static void main(String[] args) {
    
            System.out.println(MyChild1.str);
            //System.out.println(MyChild1.str2);
    
        }
    
    }
    
    
    class MyParent1 {
    
        public static String str = "hello world";
    
        static {
            System.out.println("MyParent1 block");
        }
    
    }
    
    
    class MyChild1 extends MyParent1 {
        public static String str2 = "welcome";
    
        static {
            System.out.println("MyChild1 block");
        }
    
    }
    
    

    执行以上代码,得到的输出结果是

    MyParent1 block
    hello world
    

    为什么调用了 MyChild1 类,却没有初始化MyChild1,执行MyChild1的static代码块呢

    因为针对静态变量来说,只会将定义了该字段的类进行初始化操作,而该列子中,str字段是在MyParent1类中定义的,所以这次主动调用中,会调用MyParent1的初始化操作,执行static静态代码块。

    依然是上述例子,如果这样配置,得出什么结果呢?

    //System.out.println(MyChild1.str);
    System.out.println(MyChild1.str2);
    

    执行以上代码,得到的输出结果是

    MyParent1 block
    MyChild1 block
    welcome
    

    过程分析:

    调用了Child1中的静态变量,则会导致Child1的初始化操作,但是针对类而言,当前类初始化之前必须保证其族谱类都初始化完毕才行,所以,会导致其父类MyParent1类的初始化操作,执行其静态代码块。

    2. 针对静态常量的类加载过程分析(这里是指在编译期就能获取值的情况)

    package jvmtest;
    
    /**
     * @author ztkj-hzb
     * @Date 2019/11/4 10:42
     * @Description
     */
    public class Test3 {
    
        public static void main(String[] args) {
            System.out.println(MyTest3.str);
        }
    
    }
    
    class MyTest3{
    
        public static final String str = "hello world";
    
        static {
            System.out.println("MyTest3...");
        }
    
    }
    
    

    执行以上代码,得到的输出结果是:

    hello world
    

    分析其过程:

    由上一个例子而言,如果是静态变量的话,这里一定会输出静态代码块内容,因为属于主动调用,但是这里是静态常量,且注意,这里的静态常量str的值可以再编译器获取到,即"hello world",所以,在main中的MyTest3.str 在编译期间,已经被放入到了 Test3类的静态常量池中,在执行main中,不会主动调用MyTest3了,所以不会导致MyTest3的初始化操作。我们在运行后,删除掉MyTest3.class 文件,再次执行,发现依然可以得到结果,表明确实运行时不依赖MyTest3这个类。甚至,我们可以使用javap 反编译工具,看看字节码指令运行过程。

    使用javap -c反编译工具可以看到结果:

    javap -c Test3
    警告: 二进制文件Test3包含jvmtest.Test3
    Compiled from "Test3.java"
    public class jvmtest.Test3 {
      public jvmtest.Test3();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."<init>":()V
           4: return
    
      public static void main(java.lang.String[]);
        Code:
           0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
           3: ldc           #4                  // String hello world
           5: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
           8: return
    }
    
    

    可以看到main中的 ldc(助记符),结果是个已知值,hello world

    3. 针对静态常量的类加载过程分析(这里是指在编译期不能获取值的情况)

    package jvmtest;
    
    import java.util.UUID;
    
    /**
     * @author ztkj-hzb
     * @Date 2019/11/4 15:54
     * @Description 针对静态常量,如果其值是在编译期间可以得出结果的,则会在调用该常量的所在方法的所在类的常量池中存储该值,并不会引用常量所在类。
     *                          如果其值在编译期间不能得出结果的,则会从声明该常量的类中获取,导致其引用时初始化。
     */
    public class Test4 {
    
        public static void main(String[] args) {
            System.out.println(MyTest4.str);
        }
    }
    
    class MyTest4{
    
        public static final String str = UUID.randomUUID().toString();
    
        static {
            System.out.println("MyTest4....");
        }
    }
    
    

    执行以上代码,得到的输出结果是:

    MyTest4....
    61f48050-a502-492e-8678-cf82d708253d
    

    分析其过程:

    由上一个例子结果可以看出,针对静态常量而言,如果常量的值是在编译期间可以获取的结果的,不会导致常量所在类的初始化操作,因为这种情况,会在编译时将该静态常量的值放入到调用该静态常量的方法的所在类的常量池中,可以在调用时直接获取。 现在在本例子中,静态常量的值在编译期间不能获取到值,所以首次调用时在调用类的常量池中没有改常量,因此,需要主动调用该常量的所在类,因此满足主动调用的7种情况之一,会导致MyTest4类的初始化,即静态代码块会被执行,得到上述的结果。

    4. 数组类型本质分析

    package jvmtest;
    
    /**
     * @author ztkj-hzb
     * @Date 2019/11/4 17:34
     * @Description 针对数组而言,jvm会自动生成一个jvm识别的类型,这里不会去主动调用MyTest5这个类(虽然这个类会被加载)
     */
    public class Test5 {
    
    
        public static void main(String[] args) {
    
            MyTest5[] myTest5s = new MyTest5[1];
            System.out.println(myTest5s.getClass());
    
            System.out.println("====================================");
    
            MyTest5[][] myTest5s1 = new MyTest5[1][2];
            System.out.println(myTest5s1.getClass());
        }
    
    }
    
    
    class MyTest5 {
        static {
            System.out.println("MyTest5...");
        }
    }
    
    

    执行以上代码,得到的输出结果是:

    class [Ljvmtest.MyTest5;
    ====================================
    class [[Ljvmtest.MyTest5;
    

    分析,为什么初始化MyTest5这个类(如何看出?:因为MyTest5这个类的静态代码块没有执行)

    针对数组类型,运行时会分配内存空间,但是其类型是jvm自定的 [L 开头的类型,并没有使用MyTest5这个类型,所以没有主动加载MyTest5,因此不会初始化。

    5. 针对接口类型,运行时分析

    package jvmtest;
    
    /**
     * @author ztkj-hzb
     * @Date 2019/11/5 11:33
     * @Description
     */
    public class Test6 {
        public static void main(String[] args) {
            System.out.println(MyChild6.b);
        }
    }
    
    interface MyParent6{
        public static final int a = 5;
    }
    
    interface MyChild6 extends MyParent6{
        public static final int b = 6;
    }
    
    

    执行以上代码,结果如下:

    6
    

    分析过程:

    针对接口而言,因为其变量都是默认的静态常量的类,所以理论跟示例2和示例3类似

  • 相关阅读:
    【统计学】第七章
    【统计学】第六章
    【统计学】第五章
    【统计学】第四章
    【统计学】第三章
    【统计学】第二章
    MYSQL基础
    股票数据Scrapy爬虫
    Scrapy爬虫基本使用
    Scrapy爬虫框架
  • 原文地址:https://www.cnblogs.com/duguxiaobiao/p/12091953.html
Copyright © 2020-2023  润新知