Java static关键字解析
范围:只围绕外部类进行讨论,例如内部类的内部类这种多层套娃式的暂不进行讨论
jdk版本: jdk8
静态内部类(默认指外部类的内部类)
非静态内部类(默认public),称为实例内部类,使用时需要先实例化外部类
NonStaticNestedNClass nonStaticNestedClass = new OuterClass().new NonStaticNestedClass();
静态内部类,使用:
- 引入所在外部类
import com.ergou.OuterClass
StaticNestedClass staticInnerClass = new OuterClass().new StaticNestedClass();
- 指定所在外部类的全限定名
com.ergou.OuterClass.StaticNestedClass staticNestedClass = new com.ergou.OuterClass.StaticNestedClass();
创建一个外部类OuterClass,并在外部类里分别创建非静态内部类NonStaticNestedClass和静态内部类StaticNestedClass
public class OuterClass {
public static class StaticNestedClass{}
public class NonStaticNestedClass{}
}
javac OuterClass.java,可以得到3个字节码
OuterClass.class
OuterClass$StaticNestedClass.class
OuterClass$NonStaticNestedClass.class
字节码命名已经很明显,使用javap -c分别反编译三个字节码文件
可以看到,静态内部类和外部类其实没啥区别,就是引用的时候需要指定所在外部类,和外部类引入依赖包一样。
而非静态内部类,编译时会在类中添加一个所在外部类的实例字段,在调用构造方法时将外部类实例作为入参,非静态内部类中含有一个外部类的实例引用,所以需要实例化外部类,才能实例化非静态内部类,而静态内部类则可以直接实例化。
静态方法
非静态方法称为“对象方法/实例方法”,静态方法则称为“类方法”。非静态的方法,必须实例化才能调用,而静态方法,可以直接通过全限定名或者引入所在类进行调用。
带着问题往下看:为什么方法中只有静态方法可以直接调用?
首先认识一下虚拟机中方法调用字节码指令:
invokestatic:调用静态方法
invokespecial:调用实例构造方法、私有方法和父类中的方法
invokevirtual:调用所有虚方法
invokeinterface:调用接口方法,会在运行阶段确定实现接口的对象,然后调用该对象的方法
invokedynamic:动态调用点限定符(Dynamically-Computed Call Site Specifier),必须执行到该指令时,才能动态解析出对应的调用方法。jdk7引入的新指令,也是jdk8的Lambda技术实现支撑。
*.java文件在编译时,对应不同类型的方法使用不同的调用指令(字节码中数据大多以“表”的形式存储,奈何道行不够深,不能用精辟的语言正确描述,所以只能意会了)进行调用。
字节码中,所有方法都是使用符号引用1 存储,在类加载的“解析”阶段才会将所有的符号引用解析为直接引用(内存指针或指针句柄),也就是方法区中的可真正使用的内存地址。
按照我们熟知的语法分类,静态方法、实例构造方法、私有方法和父类中的方法,这些方法在我们调用的时候就可以明确是执行哪个方法,也就是说方法是唯一的,这些方法统称为“非虚方法(Non-Virtual Method)” 2,相对应的指令是 invokestatic 和 invokespecial ,规范其实就是,只要被invokestatic和invokespecial指令调用,在解析阶段就可以将方法的符号引用翻译为方法区中确定的内存地址(内存指针或指针句柄)。代码执行的时候可以直接使用方法区内该方法的数据结构入栈执行。
与之相反的就是“虚方法(Virtual Method)”,即解析阶段时无法明确其运行方法,也就是“多态”。
虚方法的调用,涉及到“分派”确定真实的调用方法地址,内容多且没理顺,就先不往下讲了
扯得有点远了,不过也是为了深刻理解何为静态方法做铺垫,以上说的其实就是静态方法的规范的“前提条件”。
现在的问题已经缩小到了:为什么非虚方法在解析阶段可以明确了方法在方法区中的位置,但是只有静态方法可以直接调用?
PS: 这里我没捋透彻,没讲明白,可以去看看这篇博文 《静态方法、实例方法和虚方法的区别》
回到非虚方法的字节码调用指令
通过反编译,可以看到非静态方法的调用指令是invokevirtual,静态方法的调用指令为invokestatic。
final是一个附加属性,指定方法不能被重写(Override)。
静态方法是属于类的,而非静态方法是属于对象的。
类加载过程中,完成加载、连接(验证->准备->解析)和初始化(类变量赋值动作和静态语句块)阶段后,会将类的类型信息、常量、静态变量存储到方法区,然后在堆新建一个这个类的Class对象,作为方法区该类的相关方法调用入口。
以上流程结束后,才能开始在向虚拟机申请内存,等分配好内存后,才能进行我们定义的类构造函数进行初始化,到此才算真正完成了类的实例化。
这样就很容易理解为什么Java规范约束静态方法中只能使用局部变量和静态变量了,没实例化前引用的数据都是系统和编译器默认的数据嘛。
理解了规范流程,就能明白为什么只有静态方法可以直接调用了。
静态变量
静态变量只会在随着所在类的类型信息存储在方法区,线程共享,需要注意线程安全问题。
不能修饰基本数据类型。
调用需要使用全限定名或者引入所在类即可。
静态语句块
静态方法里解释了,静态语句块会在类加载过程中的初始化阶段执行,限于我们定义的类构造函数执行,因为类只会加载一次,所以静态语句块只会执行一次。
感谢周志明前辈的《深入理解Java虚拟机 JVM高级特性与最佳实践》!!!
【以上内容为本人查阅博文、文档和书籍的理解思路整理,有不正确的地方望指出,共同讨论进步】