类加载
现有如下代码
/*
* 对于静态字段来说,只有直接定义了该字段的类才会被初始化
* 当一个类在初始化时,要求其父类全部都已经初始化完毕了
* -XX:+TraceClassLoading,用于追踪类的加载信息并打印出来
*/
public class MyTest{
public static void main(String[] args) {
System.out.print("输出MyChild.str1\n");
System.out.println(MyChild.str1);
}
}
class MyParent{
public static String str1 = "hello world";
static {
System.out.println("MyParent static block");
}
}
class MyChild extends MyParent{
public static String str2 = "welcome";
static {
System.out.println("MyChild static block");
}
}
由上一篇博客可知此时父类被主动使用,即父类MyParent初始化,而子类MyChild未初始化,但是子类是否被加载了?
使用-XX:+TraceClassLoading
可以查看子类是否加载。
Eclipse程序下右击后进行如下选择
打开后点击Arguments,然后在VM arguments栏中输入上述代码,点击Apply
然后程序会输出许多信息,由以下信息可知子类和父类都被加载了
JVM参数
所有的JVM参数都以 -XX:
开始,若后面为
+<option>
,表示开启option选项-<option>
,表示关闭option选项<option>=<value>
,表示将option选项的值设置为value
常量
public class MyTest2 {
public static void main(String[] args) {
System.out.println(MyParent2.str);
}
}
class MyParent2{
public static final String str = "hello world";
static {
System.out.println("MyParent static block");
}
}
此时输出为
在str
前加上final
关键字后
// 常量在编译阶段会被存到调用这个常量的方法的类的常量池中
public class MyTest2 {
public static void main(String[] args) {
System.out.println(MyParent2.str);
}
}
class MyParent2{
public static final String str = "hello world";
static {
System.out.println("MyParent static block");
}
}
此时输出为
即MyParent2
未被初始化。
解释:final
表示一个常量,即str
被赋值后就不能再被改变了。因此在编译阶段,常量str
会被存入到调用该常量的方法所在的类的常量池中。即常量str
被存入到main
方法所在的MyTest2
的常量池当中。因此MyParent2
没有被初始化。
重点:
- 常量会被存入到调用该常量的方法所在的类的常量池中
- 本质上,调用类并没有直接引用到定义该常量的类,因此不会触发定义常量的类的初始化
- 这里是将常量
str
存放到MyTest2
的常量池中,之后MyTest2
与MyParent2
就没有任何关系了 - 甚至我们可以在运行一次后将MyParent2的class文件删除,而不会对结果产生任何影响
此时打开Terminal进入class文件所在的目录,使用javap -c MyTest2.class
对class文件反编译的到下图
对比不加final
关键字时的反编译信息
可见助记符ldc
后面是一个String类型的字符串,而不再是MyParent2.str的变量
ldc
:表示将int,float或是String类型的常量值从常量池中推送至栈顶
若使用如下代码:
public class MyTest2 {
public static void main(String[] args) {
System.out.println(MyParent2.s);
}
}
class MyParent2{
public static final short s = 7;
static {
System.out.println("MyParent static block");
}
}
易知输出为7,反编译后得
bipush
:表示将单字节(-128 ~ 127)得常量值推送至栈顶
public class MyTest2 {
public static void main(String[] args) {
System.out.println(MyParent2.i);
}
}
class MyParent2{
public static final int i = 128;
static {
System.out.println("MyParent static block");
}
}
反编译后得
sipush
:表示将一个短整型常量值(-32768 ~ 32767)推送至栈顶
public class MyTest2 {
public static void main(String[] args) {
System.out.println(MyParent2.m);
}
}
class MyParent2{
public static final int m = 1;
static {
System.out.println("MyParent static block");
}
}
反编译得
iconst_1
:表示将int类型1推送至栈顶(iconst_1 ~ iconst_5)
总结
常量
若使用 final
关键字,则得到常量。在编译阶段,常量会被存入到调用该常量的方法所在的类的常量池中,再次调用此常量时不一定会初始化定义其值的类,而是初始化调用该常量的方法所在的类。
助记符
ldc
:表示将int,float或是String类型的常量值从常量池中推送至栈顶bipush
:表示将单字节(-128 ~ 127)得常量值推送至栈顶sipush
:表示将一个短整型常量值(-32768 ~ 32767)推送至栈顶iconst_1
:表示将int类型1推送至栈顶(iconst_1 ~ iconst_5)