Después de escribir código java durante tantos años, ¿sabe realmente cómo se cargan las clases?


Inserte la descripción de la imagen aquí

1. Prueba de valor constante

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

La conclusión es:

  1. El valor constante se almacenará en el grupo constante de la clase llamada, por lo que la clase llamada no se utilizará activamente, por lo que la clase llamada no se inicializará. El
    UUID.randomUUID().toString()valor de esta declaración no se fija durante la compilación, por lo que no será como la mencionada anteriormente.(public static final String str = "I Am ClassLoaderTest1";)
  2. El valor constante se almacenará en el grupo constante de la clase que llama, por lo que la clase llamada no se usará activamente, por lo que la clase llamada no se inicializará
    Inserte la descripción de la imagen aquí

2. Prueba de valor de variable estática

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

La conclusión es:

  1. Acceder a las variables estáticas de una determinada clase o interfaz es equivalente al uso activo de la clase (explicado en el artículo anterior), por lo que la clase llamada se usará activamente, por lo que la clase llamada se inicializará y generaráFinalTest static block
  2. Tenga en cuenta que las variables estáticas no son lo mismo que las constantes
    Inserte la descripción de la imagen aquí

3. Crear prueba de objeto

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
	 ------------------------------
  }

La conclusión es:

  • Una clase usará activamente la clase llamada (ClassLoaderTest2) cuando sea nuevo un objeto, y una clase solo se inicializará una vez cuando sea nuevo un objeto
    Inserte la descripción de la imagen aquí

4. Crear prueba de datos de matriz de objetos

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
  }

La conclusión es:

  • La ClassLoaderTest2 Blockpalabra no aparece como se esperaba , por lo que todas las clases, excepto la clase de matriz, son creadas por el cargador de clases. El tipo de datos es generado dinámicamente por el tiempo de ejecución de jvm, y ***[LclassLoader.ClassLoaderTest2t;las palabras expresadas como una matriz de dos dígitos son [[** (Nota: hay un punto y coma después). El tipo generado dinámicamente, el super tipo es. java.lang.ObjectPara las matrices, JavaDoc a menudo se refiere a los elementos que componen la matriz como Componente, que en realidad es el tipo después de que la matriz se reduce en una dimensión.

5. Cree un tipo básico de prueba de datos de matriz

 @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
  }

En conclusión:

  • El súper tipo es java.lang.Objectpara matrices. JavaDoc a menudo se refiere a los elementos que constituyen la matriz como Componente, que en realidad es el tipo después de que la matriz se reduce en una dimensión.
    Inserte la descripción de la imagen aquí

6. Utilice el ejemplo del patrón singleton para simular el proceso de carga de clases.

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
    */
 }

Inserte la descripción de la imagen aquí


7. Inicializar al probar la herencia de la interfaz

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
    * 所以得出结论是:
    *   只有在真正使用到父接口的时候(如引用接口中定义的常量时),才会初始化。注意如果是类与类继承的时候如果子类进行初始化的时候
    *   父类必须进行初始化
    */
 }

En conclusión:

  1. Es decir, la interfaz secundaria no tiene nada que ver con la interfaz principal, por lo que se concluye que la inicialización de la interfaz secundaria no requiere necesariamente que la interfaz principal complete la inicialización.
  2. Solo se inicializará cuando se utilice realmente la interfaz principal (como cuando se hace referencia a una constante definida en la interfaz).
  3. Tenga en cuenta que si la clase se hereda de la clase, la clase principal debe inicializarse cuando se inicializa la subclase

8. Inicialice una herencia de clase de prueba

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
}

La conclusión es:

  1. Cuando un objeto es nuevo, se inicializará la clase actual, pero la premisa es que la clase actual no se ha inicializado (presentado en el capítulo anterior)
  2. Acceda activamente a las variables estáticas de esta clase, y la clase llamada también se inicializará activamente (descrito anteriormente)

9. Inicialice dos al probar la herencia de clases

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
}

La conclusión es:

  1. Acceda activamente a las variables estáticas de esta clase, y la clase llamada también se inicializará activamente (descrito anteriormente)

  2. Llamar al método estático de la clase también es un uso activo de la clase, pero la clase ya se ha inicializado una vez cuando se llama a la variable estática, por lo que la segunda vez que la clase no se inicializará, no se emitirá Parent static block

    Inserte la descripción de la imagen aquí
    En este punto, todos deberían comprender el proceso general de carga de clases. Si tiene alguna pregunta, espero dejar un mensaje

Présteme atención, seguiré publicando ... Espero con ansias el próximo número de aprendizaje de JVM.
Animo con usted ...

Supongo que te gusta

Origin blog.csdn.net/weixin_38071259/article/details/105893659
Recomendado
Clasificación