常量池
Java中的常量池分为两种,一种是静态常量池,一种是运行时常量池。
静态常量池
是class文件中的常量池,包含字符串(数字)字面量,类,方法的信息。占用class文件的绝大部分空间。
这种常量池主要存放两大类常量:字面量和符号引用量。
字面量:
字面量可以理解为实际值,也可以理解为描述了自己的量。比如int a=3;3就是字面量。再比如String s=”abc“;abc就是字面量
符号引用量:
包括类和接口的全限定名。
字段名称和描述符。
方法名称和描述符
限定类名:就是类名全称,带包路径的用点隔开,例如java.lang.String
非限定类名:就是平时所说的类名,不带包的。如String。
运行时常量池
指的是jvm虚拟机完成类的加载工作时,将class文件中的常量池载入到内存的方法区中,并保存下来。我们经常说的常量池,指的就是这个。
运行时常量池具有*动态性,*意思是指,java中的常量并不一定必须在编译时才能产生。在程序执行的过程中也可能将常量放入常量池中。
另注:其实运行时字符串常量池中的常量,基本来源于各个class文件中的常量池。程序运行时,除非手动向常量池中添加常量(比如使用intern方法),否则jvm不会自动添加常量到常量池。
常量池的优点
1.常量池的存在可以避免频繁的创建和销毁对象,从而影响程序性能。
2.节省内存空间。以字符串常量池为例:常量池中所有的相同的字符串常量通常会被合并。只占用一个空间。
3.节省运行时间。比较字符串时比equals快。对于两个引用型变量。只用判断引用是否相等。就可以判断值是否相等。
另注:java中直接使用==比较的是两个字符串的引用地址。并不是比较内容。比较内容可以使用String.equals()
常量池的一些例子
public class Test {
public static void main(String[] args) {
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);//ture
System.out.println(s1==s3);//ture
System.out.println(s1==s4);//false
System.out.println(s1==s9);//false
System.out.println(s4==s5);//false
System.out.println(s1==s6);//ture
}
}
分析:
s1==s2,s1和s2在编译时作为字面量进入静态常量池。随后被加载进方法区中的动态常量池中,此时,二者指向的是同一个地址值。所以为ture
s1==s3,s3为已知的字符串常量拼接而成。在编译时会自动优化成”hello“存储在class文件中。即为string s3=“hello”所以为ture
另注:只有使用引号包含文本的方式创建的String对象之间使用+连接产生的新对象才会被加载进字符串常量池中。
s1==s4,s4虽然也是拼接,但是包含new方式创建对象。编译时new String(“lo”)是变量,是不可知的。编译器不会优化。所以不会加载进运行时常量池,而是在堆内存中分配空间。地址当然不同。结果为false
(s1==s9),同理,s7,s8编译时是变量,不可知,所以不会加载进运行时常量池。在堆内存中分配空间。地址不同,结果为false。
(s4==s5),虽然都在堆内存中,但是地址值 不相同。结果为false
(s1==s6),intern()方法的作用是:查找常量池中是否有equals相等的字符串。如果有则返回字符串的引用。如果没有就添加自己的字符串进常量池。常量池中有“hello”所以返回常量池中的地址值。与s1地址值相同。结果为ture