非静态内部类中 static/final 成员变量相关知识

最近看了一个帖子,问为什么非静态内部类中不能有 static 成员变量却可以有 static final 属性的编译期常量,看起来似乎很简单,实际上却是一箭双雕的一道题,即考察了非静态内部类相关知识,还考察了 final 的各种常量分类细则,因为很多人回答时会疏忽或者已经忘记了基础的这个点,在这里梳理一下相关知识,我也巩固一下基础。

由于 Java 中非静态内部类默认持有了外部类引用,也就是说可以将它看成是其外部类的一个成员,所以其必须跟外部类实例相关联才能初始化。而静态成员是属于类层次的,是不需要类实例就可以初始化访问的。此时如果假设让一个非静态内部类拥有了静态变量,则其应该可以不依托于任何外部类实例就能访问,而非静态内部类却没法做到不实例化其外部类而使用,所以这种设计从语法层面就是互斥的。

而对于 static final 成员来说就不一样了,至于为什么不一样我们需要先切换补充一些知识点。

《非静态内部类中 static/final 成员变量相关知识》

上面的程序代码段分别编译运行的结果在注释部分已经给出了,编译器也帮我们提示了异常信息,在我们挨个解释上面语句现象前先说说 Java 对常量的一些定义和处理机制

对于Java中的常量其实可以分为编译期常量和非编译期常量编译期常量是指,在程序编译阶段(不需要加载类的字节码)就可以去确定具体值的常量,其中会涉及到编译期常量折叠(编译器可以根据语法分析计算出值的常量表达式进行计算赋值)。非编译期常量(运行期常量)是指,在程序运行阶段(需要加载类的字节码),才可以确定具体值的常量(编译期无法折叠,编译器只对所有可能修改它的地方进行检查和报错)。

当我们通过类名访问被 static final 修饰的常量时,如果该变量为编译期常量则该类不会被JVM加载,如果该常量为非编译期常量则该类会被JVM加载,当通过类名访问被 static 修饰的变量时,都会触发该类被JVM加载。

有了上面这个概念我们再来看上面代码段的原因,由于 Java 类属性的初始化顺序为(静态变量、静态初始化块)>(变量、初始化块)> 构造器,所以 JVM 要求所有的静态属性必须在类对象创建之前完成初始化,所以对于 t1 属性调用来说,必须要等到外部类OuterClass实例化之后,也就是说创建了一个外部类对象之后,JVM才能加载内部类InnerClass的字节码,而我们调用的地方并没有对OuterClass进行实例化,所以InnerClass也不会被加载,而t1是static属性,要初始化t1就必须先加载InnerClass的字节码,而 t3 是非编译期常量,要初始化它们也必须加载InnerClass的字节码才能确定它们的值,只有 t2 是编译时常量,所以不会触发内部类加载机制,所以有了上面代码段的结果。

所以非静态内部类中不能有static 成员变量,却可以有static final 属性的编译期常量,而不能有static final 属性的运行期常量。

那么问题来了,为什么我们在类中定义static 修饰的String 常量时经常会在它前面加上final 修饰符?那是因为可以使JVM不必加载该类就能直接使用其值,从而节省内存空间。

    原文作者:AndyRenJie
    原文地址: https://blog.csdn.net/qq_20328181/article/details/81391956
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞