【注解和反射】什么时候类会和不会被初始化?

继上一篇博客【注释和反射】类加载的过程-CSDN博客

目录

四、什么时候类会被初始化(主动引用)?

测试

五、什么情况下不会发生类的初始化(被动引用)?

测试


四、什么时候类会被初始化(主动引用)?

在Java中,类的初始化是类加载过程的一部分,具体地说,它发生在类加载的"初始化"阶段。但是,类的初始化并不会在类被加载到JVM时立即发生,而是在以下情况之一发生时才会被触发:

  1. 创建类的实例 :当使用new关键字创建类的新实例时,如果该类还没有被初始化,则会触发类的初始化。

  2. 访问某个静态变量 :当类中的静态变量被访问时(除非该静态变量是常量,即被final修饰的基本类型或字符串常量),如果该类尚未初始化,则会触发初始化。需要注意的是,只有真正读取静态变量的值才会触发初始化,而仅仅声明静态变量不会触发初始化。

  3. 调用类的静态方法:当调用类的静态方法时,如果该类还没有被初始化,则会先进行初始化。

  4. 反射 :当使用Java反射API(如Class.forName()方法)显式加载类时,会触发类的初始化。这是框架和库常用的技术,用于动态加载和初始化类。

  5. 初始化子类 :当初始化一个类时,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。

  6. Java虚拟机启动时 :当Java虚拟机启动时,会先初始化包含main方法的那个类。

  7. 被标记为@interface的接口 :在Java中,接口在加载后、初始化前,会为接口中定义的常量(只能是public static final)进行赋值。然而,接口本身并不需要进行传统意义上的"初始化",因为它们不能有实例字段或实例初始化块。

需要注意的是,

  1. 静态代码块(static {})会在类的初始化过程中执行,且只执行一次。
  2. 静态代码块的执行顺序是按照它们在类中出现的顺序。
  3. 此外,静态变量的初始化也是类初始化的一部分,静态变量会在任何静态方法(包括静态代码块)被调用之前初始化,除非该变量是常量(final修饰的静态变量)。

了解这些触发条件有助于更好地理解和控制Java程序的类加载和初始化行为。

测试

首先,创建一个父类和继承它的子类。

java 复制代码
class F{
  static int b = 2;
  static {
    System.out.println("父类被加载");
  }
}

class S extends F{
  static {
    System.out.println("子类被加载");
    m = 300;
  }
  static int m = 100;
  static final int M = 1;
}

(1) 当使用new关键字创建类的新实例时,如果该类还没有被初始化,则会触发类的初始化。

java 复制代码
public class Test03 {
  static {
    System.out.println("main类被加载");
  }

  public static void main(String[] args) {
    S s = new S();
    
  }
}

(2)反射也会触发类的初始化

java 复制代码
public class Test03 {
  static {
    System.out.println("main类被加载");
  }

  public static void main(String[] args) throws ClassNotFoundException {
    Class.forName("com.itheima.sjms.S");

  }
}

五、什么情况下不会发生类的初始化(被动引用)?

在Java中,有几种情况下类的引用并不会触发类的初始化,这些情况通常被称为"被动引用"。以下是被动引用的几种情况:

  1. 通过子类引用父类的静态字段:如果仅通过子类来引用父类中的静态字段,只会触发父类的初始化,而不会触发子类的初始化。这是因为静态字段属于类本身,而不属于类的任何实例。

  2. 通过数组定义来引用类 :当定义一个类的数组,但并未实际创建该类的实例或访问其静态字段/方法时,不会触发类的初始化。例如,MyClass[] array; 这行代码本身不会导致MyClass的初始化。

  3. 常量在编译阶段的引用 :如果一个类的静态常量(被final修饰的静态变量,且其值在编译时已知)被另一个类引用,那么在编译阶段,这个常量的值会被直接嵌入到引用它的类中,因此不会触发定义常量的类的初始化 。这是因为编译器在编译时就已经将常量的值替换到了引用它的代码中,这个过程称为常量折叠

  4. 仅加载类而不初始化 :在某些情况下,Java虚拟机可能仅加载类而不初始化它。例如,当使用ClassLoader类的loadClass方法来加载类时,可以选择是否进行类的初始化。如果传递的参数为false,则仅加载类而不进行初始化。

  5. 接口类型的引用:接口的引用通常不会触发接口的初始化。接口本身不包含实例字段,因此它们不需要像类那样进行初始化。只有当接口中定义的常量被实际使用时,接口才会被加载和初始化。

了解这些情况有助于优化Java程序的性能,因为可以避免不必要的类加载和初始化开销。同时,这也有助于理解Java类加载器和类加载机制的内部工作原理。

测试

首先,创建一个父类和继承它的子类。

java 复制代码
class F{
  static int b = 2;
  static {
    System.out.println("父类被加载");
  }
}

class S extends F{
  static {
    System.out.println("子类被加载");
    m = 300;
  }
  static int m = 100;
  static final int M = 1;
}

(1)通过子类调用父类的一个静态变量,

java 复制代码
public class Test03 {
  static {
    System.out.println("main类被加载");
  }

  public static void main(String[] args) {
    System.out.println(S.b);
  }
}

此时不会对子类产生任何影响,子类都不会被加载。

(3)创建一个子类的数组,

java 复制代码
public class Test03 {
  static {
    System.out.println("main类被加载");
  }

  public static void main(String[] args) {
    S[] arr = new S[5];
  }
}

我们可以看到,只有main类被加载了,因为虚拟机启动的时候,先会初始化main方法所在的类。初始化完之后,S[] arr = new S[5];执行这行代码,没有任何类被加载,因为这个数组只是一个名字和一片空间而已,并不会加载类。

(3)调用子类的常量,

java 复制代码
public class Test03 {
  static {
    System.out.println("main类被加载");
  }

  public static void main(String[] args) {
    System.out.println(S.M);
  }
}

可以看到代码的执行结果中,子类和父类都没有被加载,因为所有的常量和类的静态变量都是在链接阶段就已经赋值了,所以初始化的时候已经存在了。

相关推荐
SilentSamsara24 分钟前
Python 环境搭建完整指南:从下载安装到运行第一个程序
开发语言·python
曹牧33 分钟前
Spring:@RequestMapping注解,匹配的顺序与上下文无关
java·后端·spring
daixin884835 分钟前
cursor无法正常使用gpt5.5等模型解决方案
java·redis·cursor
小短腿的代码世界37 分钟前
Qt文件系统与IO深度解析:从QFile到异步文件操作
开发语言·qt
韦禾水1 小时前
记录一次项目部署到tomcat的异常
java·tomcat
曦月合一2 小时前
树莓派安装jdk、tomcat、vnc、谷歌浏览器开机自启等环境配置
java·tomcat·树莓派
harder3212 小时前
RMP模式的创新突破
开发语言·学习·ios·swift·策略模式
jinanwuhuaguo2 小时前
OpenClaw工程解剖——RAG、向量织构与“记忆宫殿”的索引拓扑学(第十三篇)
android·开发语言·人工智能·kotlin·拓扑学·openclaw
Rust研习社2 小时前
使用 Axum 构建高性能异步 Web 服务
开发语言·前端·网络·后端·http·rust
此剑之势丶愈斩愈烈2 小时前
openssl 自建证书
java