常量池分类
常量池大体可以分为:静态常量池,运行时常量池。
静态常量池: 存在于class文件中,比如经常使用的javap -verbose中,常量池总是在最前面把?
运行时常量池:就是在class文件被加载进了内存之后,常量池保存在了方法区中,通常说的常量池 值的是运行时常量池。所以呢,讨论的都是运行时常量池
字符串常量池
最最最流行的、最典型的就是字符串了
典型范例:
String a = "abc";
String b = new String("abc");
System.out.println(a == b);
----*----
结果:false
这个是第一个需要理解的地方,a指向哪片内存,b又指向哪片内存呢?对象储存在堆中,这个是不用质疑的,而a作为字面量一开始储存在了class文件中,之后运行期,转存至方法区中。它们两个就不是同一个地方存储的。知道了它之后我们就可以通过实例直接进一步了解了
实例
String s1 = "Hello";
String s2 = "Hello";
String s3 = "Hel" + "lo";
String s4 = "Hel" + new String("lo");
String s5 = new String("Hello");
String s6 = s5.intern();
String s7 = "H";
String s8 = "ello";
String s9 = s7 + s8;
System.out.println(s1 == s2); // true
System.out.println(s1 == s3); // true
System.out.println(s1 == s4); // false
System.out.println(s1 == s9); // false
System.out.println(s4 == s5); // false
System.out.println(s1 == s6); // true
分析:
1、s1 = = s2 很容易可以判断出来。s1 和 s2 都指向了方法区常量池中的Hello。
2、s1 = = s3 这里要注意一下,因为做+号的时候,会进行优化,自动生成Hello赋值给s3,所以也是true
3、s1 = = s4 s4是分别用了常量池中的字符串和存放对象的堆中的字符串,做+的时候会进行动态调用,最后生成的仍然是一个String对象存放在堆中。
4、s1 = = s9
在JAVA9中,因为用的是动态调用,所以返回的是一个新的String对象。所以s9和s4,s5这三者都不是指向同一块内存
5、s1 = = s6 为啥s1 和 s6地址相等呢? 归功于intern方法,这个方法首先在常量池中查找是否存在一份equal相等的字符串如果有的话就返回该字符串的引用,没有的话就将它加入到字符串常量池中,所以存在于class中的常量池并非固定不变的,可以用intern方法加入新的;
需要注意的特例
1、常量拼接
public static final String a = "123";
public static final String b = "456";
public static void main(String[] args)
{
String c = "123456";
String d = a + b;
System.out.println(c == d); //true
}
------反编译结果-------
0: ldc #2 // String 123456
2: astore_1
3: ldc #2 // String 123456
5: astore_2
6: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
我们可以发现,对于final类型的常量它们已经在编译中被确定下来,自动执行了+号,把它们拼接了起来,所以就相当于直接”123” + “456”;
2、static 静态代码块
public static final String a;
public static final String b;
static {
a = "123";
b = "456";
}
public static void main(String[] args)
{
String c = "123456";
String d = a + b;
System.out.println(c == d); //false
}
------反编译结果-------
3: getstatic #3 // Field a:Ljava/lang/String;
6: getstatic #4 // Field b:Ljava/lang/String;
9: invokedynamic #5, 0 // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
上个例子是在编译期间,就已经确定了a和b,但是在这段代码中,编译期static不执行的,a和b的值是未知的,static代码块,在初始化的时候被执行,初始化属于类加载的一部分,属于运行期。看看反编译的结果,很明显使用的是indy指令,动态调用返回String类型对象。一个在堆中一个在方法区常量池中,自然是不一样的。
参考:https://blog.csdn.net/qq_41376740/article/details/80338158