深入理解Java虚拟机2:常量的本质含义与反编译及助记符详解

类加载

现有如下代码

/*
 *  对于静态字段来说,只有直接定义了该字段的类才会被初始化
 *  当一个类在初始化时,要求其父类全部都已经初始化完毕了
 *  -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的常量池中,之后MyTest2MyParent2就没有任何关系了
  • 甚至我们可以在运行一次后将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)

猜你喜欢

转载自blog.csdn.net/weixin_44547562/article/details/104486169