写这么多年的java代码 , 类是如何加载的你真的知道么?


在这里插入图片描述

1、常量值测试

class ClassLoaderTest {
    
    
  public static final String str = UUID.randomUUID().toString();
  static {
    
    
    System.out.println("ClassLoaderTest  Block");
  }
}

class ClassLoaderTest1 {
    
    
  public static final String str = "I Am ClassLoaderTest1";
  static {
    
    
    System.out.println("ClassLoaderTest1  Block");
  }
}
 @Test
 public void test() {
    
    
   System.out.println(ClassLoaderTest.str);
   System.out.println("-------------------------");
   System.out.println(ClassLoaderTest1.str);
 }
 输出结果:
	ClassLoaderTest  Block
    6799db17-6d67-4195-9b13-b2c4c082d86c
    -------------------------
    I Am ClassLoaderTest1

结论是 :

  1. 该常量值会被存储到被调用类的常量池中所以就不去会主动的使用被调用类,所以被调用类就不会被初始化。该语句
    UUID.randomUUID().toString() 的值在编译期间不是固定的,所以就不会像之前说的那种 (public static final String str = "I Am ClassLoaderTest1";)
  2. 该常量值会被存储到调用类的常量池中所以就不去会主动的使用被调用类, 所以被调用类就不会被初始化
    在这里插入图片描述

2、静态变量值测试

class ClassLoaderStatic {
    
    
    public static int x = 3;

    static {
    
    
        System.out.println("FinalTest static block");
    }
}
@Test
public void testStatic() {
    
    
  System.out.println(ClassLoaderStatic.x);
}
输出结果:
   FinalTest static block
   3

结论是:

  1. 访问某个类或接口的静态变量相当于对类的主动使用(前一篇文章有说明),这样一来就会主动的去使用被调用类,所以被调用类就会被初始化输出FinalTest static block
  2. 注意静态变量与常量是不一样的
    在这里插入图片描述

3、创建对象测试

class ClassLoaderTest2 {
    
    
  public static final String str = "I Am ClassLoaderTest2";

  static {
    
    
    System.out.println("ClassLoaderTest2  Block");
  }
}
@Test
public void testCreateNewObject() {
    
    
  ClassLoaderTest2 c = new ClassLoaderTest2();
  System.out.println("------------------------------");
  ClassLoaderTest2 c1 = new ClassLoaderTest2();
  输出结果:
	 ClassLoaderTest2  Block
	 ------------------------------
  }

结论是:

  • 一个类在 new 一个对象的时候会主动的去使用被调用类(ClassLoaderTest2),并且一个类在 new 一个对象的时候只会被初始化一次
    在这里插入图片描述

4、创建对象数组数据测试

class ClassLoaderTest2 {
    
    
  public static final String str = "I Am ClassLoaderTest2";

  static {
    
    
    System.out.println("ClassLoaderTest2  Block");
  }
}
@Test
 public void testCreateObjectArray() {
    
    
   ClassLoaderTest2[] cArray = new ClassLoaderTest2[1];
   System.out.println(cArray.getClass());
   System.out.println(cArray.getClass().getSuperclass());
   System.out.println("------------------");
   /** 二维数组*/
   ClassLoaderTest2[][] c2Array = new ClassLoaderTest2[1][];
   System.out.println(c2Array.getClass());
   System.out.println(c2Array.getClass().getSuperclass());
   输出结果:
      class [LclassLoader.ClassLoaderTest2;
   	  class java.lang.Object
      ------------------
      class [[LclassLoader.ClassLoaderTest2;
      class java.lang.Object
  }

结论是:

  • 并没有想象中的出现ClassLoaderTest2 Block 字样,所以就是除了数组类的其他类都是由类加载器来创建的。对于数据类型是由jvm运行期动态生成的,表示为***[LclassLoader.ClassLoaderTest2t; 二位数组的字样为[[ **(注:后面有一个因为分号)这种形式。动态生成的类型,父类型就是java.lang.Object 对于数组来说,JavaDoc 经常将构成数组的元素为 Component ,实际上是将数组降低一个维度后的类型

5、创建基础类型的数组数据测试

 @Test
  public void testCreateBaseArray() {
    
    
    int[] ints = new int[1];
    System.out.println(ints.getClass());
    System.out.println(ints.getClass().getSuperclass());
	输出结果:
       class [I
       class java.lang.Object
  }

结论:

  • 父类型就是java.lang.Object对于数组来说,JavaDoc 经常将构成数组的元素为 Component,实际上是将数组降低一个维度后的类型
    在这里插入图片描述

6、利用单例模式的例子进行模拟类加载的过程

class SingletonDemo1 {
    
    
  public static int counter1;
  public static int counter2 = 0;
  private static SingletonDemo1 singleton = new SingletonDemo1();
  
  private SingletonDemo1() {
    
    
    counter1++;
    counter2++;
  }
  public static SingletonDemo1 getInstance() {
    
    
    return singleton;
  }
}

@Test
 public void testSingletonLoad() {
    
    
   SingletonDemo1 singleton1 = SingletonDemo1.getInstance();
   System.out.println("SingletonDemo1 counter1: " + singleton1.counter1);
   System.out.println("SingletonDemo1 counter2: " + singleton1.counter2);
   输出结果:
   	  SingletonDemo1 counter1: 1
      SingletonDemo1 counter2: 1
    /* 流程分析:
    *   加载:执行 SingletonDemo1.getInstance() 第一步进行 SingletonDemo1的类加载
    *   连接:
    *        1.验证:字节码文件的正确性
    *        2.准备:将类的静态变量进行赋值(counter1,counter2 都进行空间分配再赋予初始的值 0 0)
    *        3.解析:将符号引用变成直接引用
    *   初始化:
    *        counter1++; 变成 1
    *        counter2++; 变成 1
    */
}
class SingletonDemo2 {
    
    
  public static int counter1;
  private static SingletonDemo2 singleton = new SingletonDemo2();

  private SingletonDemo2() {
    
    
    counter1++;
    counter2++;
  }
  public static int counter2 = 0;
  public static SingletonDemo2 getInstance() {
    
    
    return singleton;
  }
@Test
 public void testSingletonLoad() {
    
    
   SingletonDemo2 singleton2 = SingletonDemo2.getInstance();
   System.out.println("SingletonDemo2 counter1: " + singleton2.counter1);
   System.out.println("SingletonDemo2 counter2: " + singleton2.counter2);
   输出结果:
   	  SingletonDemo2 counter1: 1
      SingletonDemo2 counter2: 0
   /**
    * 流程分析:
    *   加载:执行 SingletonDemo1.getInstance() 第一步进行 SingletonDemo2
    *   连接:
    *        1.验证:字节码文件的正确性
    *        2.准备:将类的静态变量进行赋值(counter1,counter2 都进行空间分配再赋予初始的值 0 0)
    *        3.解析:将符号引用变成直接引用
    *   初始化:
    *        counter1++; 变成 1
    *        counter2++; 变成 1
    *   重点: public static int counter2 = 0;
    *   再一次的将 counter2 进行 0 赋值所以 counter2 的值变成了 0
    */
 }

在这里插入图片描述


7、测试接口继承时初始化

interface ClassLoaderTest3 {
    
    
  /**
   * public static final 接口中定义的变量默认就是常量
   */
  public static final String str = "i am interface parent";

}

interface ClassLoaderChild extends ClassLoaderTest3 {
    
    
  public static final String str = "i am interface child";
}
/**
	在运行之前将编译后的 ClassLoaderTest3.class 文件删除
*/
@Test
 public void testInterface() {
    
    
   System.out.println(ClassLoaderChild.str);
   System.out.println("------------------");
   System.out.println(ClassLoaderTest3.str);
   输出结果:
   	  i am interface child
   	  ------------------
   	  Error:(95, 24) java: 找不到符号
      符号:   变量 ClassLoaderTest3
      位置: 类 classLoader.JuintTestTest
    * 所以得出结论是:
    *   只有在真正使用到父接口的时候(如引用接口中定义的常量时),才会初始化。注意如果是类与类继承的时候如果子类进行初始化的时候
    *   父类必须进行初始化
    */
 }

结论:

  1. 就是子接口与父接口没有关系,所以得出结论是子接口初始化的时候并不是必须要求父接口也要完成初始化。
  2. 只有在真正使用到父接口的时候(如引用接口中定义的常量时),才会初始化。
  3. 注意如果是类与类继承的时候如果子类进行初始化的时候父类必须进行初始化

8、测试类继承时初始化一

class ClassLoaderStaticParent{
    
    
    public static final int x = 1;

    static {
    
    
        System.out.println("i am ClassLoaderStaticParent");
    }
}

class ClassLoaderStaticChild extends ClassLoaderStaticParent{
    
    
    public static int x = 2;
    static {
    
    
        System.out.println("i am ClassLoaderStaticChild");
    }
}
@Test
public void ClassLoaderStaticParentAndChild() {
    
    
  ClassLoaderStaticParent parent;
  System.out.println("-------------------");
  parent = new ClassLoaderStaticParent();
  System.out.println("------------------");
  System.out.println(parent.x);
  System.out.println("------------------");
  System.out.println(ClassLoaderStaticChild.x);
  输出结果: 
     -------------------
     i am ClassLoaderStaticParent
     ------------------
     1
     ------------------
     i am ClassLoaderStaticChild
     2
}

结论是:

  1. new一个对象的时候就会初始化当前的类,但是前提是当前类未被初始化过(上一章介绍过)
  2. 主动的去访问该类的静态变量,被调用类也会被主动的初始化(上文介绍过)

9、测试类继承时初始化二

class ClassLoaderPorpertyAndMethodParent{
    
    
   public static int a = 3;
   static{
    
    
       System.out.println("Parent static block");
   }
   static void doSomeThing(){
    
    
       System.out.println("I am doSomeThing methed");
   }
}

class ClassLoaderPorpertyAndMethodChild extends ClassLoaderPorpertyAndMethodParent{
    
    
    static {
    
    
        System.out.println("i am ClassLoaderPorpertyAndMethodChild block");
    }
}
@Test
public void ClassLoaderPorpertyAndMethodParent() {
    
    
  System.out.println(ClassLoaderPorpertyAndMethodChild.a);
  System.out.println("-------------------------");
  ClassLoaderPorpertyAndMethodChild.doSomeThing();
  输出结果: 
     Parent static block
     3
     -------------------------
     I am doSomeThing methed
}

结论是:

  1. 主动的去访问该类的静态变量,被调用类也会被主动的初始化(上文介绍过)

  2. 调用类的静态方法也是对类的主动使用,但是该类已经在被调用静态变量的时候初始话过一次了所以第二次不会对类进行初始化了,就不会输出 Parent static block

    在这里插入图片描述
    到这里针对与类加载的常规流程大家应该也都了解了,有问题希望留言

关注我,我会一直持续输出… 期待下一期的JVM的学习
与君共勉 …

猜你喜欢

转载自blog.csdn.net/weixin_38071259/article/details/105893659