何年もの間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
	 ------------------------------
  }

結論は次のとおりです。

  • クラスは、オブジェクトが新しくなったときに呼び出されたクラス(ClassLoaderTest2)をアクティブに使用し、オブジェクトが新しくなったときにクラスが初期化されるのは1回だけです。
    ここに画像の説明を挿入

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;2桁の配列として表される単語は[[**(注:その後にセミコロンがあります)です。動的に生成される型、スーパー型はです。java.lang.Object配列の場合、JavaDocは、配列を構成する要素をComponentと呼ぶことがよくありますこれは、実際には、配列が1次元縮小された後の型です。

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、配列を構成する要素をコンポーネントと呼ぶことがよくあります。これは、実際には、配列が1次元縮小された後のタイプです。
    ここに画像の説明を挿入

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. オブジェクトが新しい場合、現在のクラスは初期化されますが、現在のクラスが初期化されていないことが前提です(前の章で紹介)
  2. このクラスの静的変数にアクティブにアクセスすると、呼び出されたクラスもアクティブに初期化されます(上記を参照)

9.クラスの継承をテストするときに2つを初期化します

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. クラスの静的メソッドを呼び出すこともクラスのアクティブな使用法ですが、静的変数が呼び出されたときにクラスはすでに一度初期化されているため、2回目にクラスが初期化されない場合は出力されません Parent static block

    ここに画像の説明を挿入
    この時点で、クラスの読み込みの一般的なプロセスを全員が理解する必要があります。ご不明な点がございましたら、メッセージを残してください。

私に注意してください、私は出力を続けます... JVM学習の次の号を楽しみに
していますあなたと一緒に励ましてください...

おすすめ

転載: blog.csdn.net/weixin_38071259/article/details/105893659