Java中的常量池

Java常量池实际上分为两种形态:静态常量池和运行时常量池
    常量池是为了避免频繁的创建和销毁对象而影响系统性能,其实现了对象的共享。


静态常量池:

    静态常量池是Class文件常量池(编译后的class文件的常量池),存放各个字面量值,符号引用的数据。

    主要用于存放两大类常量:字面量(Literal)和符号引用量(Symbolic References)。

    字面量相当于Java语言层面常量的概念,如文本字符串,声明为final的常量值等。


运行时常量池(具备动态性):

    是jvm虚拟机在完成类装载操作后,将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

//对于所有包含new方式新建对象(包括null)的“+”连接表达式,它所产生的新对象都不会被加入字符串池中
System.out.println(s1 == s4);  // false

//s7、s8作为两个变量,都是不可预料的,编译器毕竟是编译器,不可能当解释器用,不能在编译期被确定
//所以不做优化,只能等到运行时,在堆中创建s7、s8拼接成的新字符串,在堆中地址不确定,不可能与方法区常量池中的s1地址相同
System.out.println(s1 == s9);  // false

//二者都在堆中,但地址不同
System.out.println(s4 == s5);  // false

//s5在堆中,内容为Hello ,intern方法会尝试将Hello字符串添加到常量池中,并返回其在常量池中的地址
//因为常量池中已经有了Hello字符串,所以intern方法直接返回地址
System.out.println(s1 == s6);  // true


特例一

public static final String A = "ab"; // 常量A
public static final String B = "cd"; // 常量B
     String s = A + B;    // 将两个常量用+连接对s进行初始化 
     String t = "abcd";    //s == t 成立
A和B都是常量,值是固定的,因此s的值也是固定的,它在类被编译时就已经确定了。
也就是说:String s=A+B; 等同于:String s="ab"+"cd";


特例二

    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);
    }

编译期间,就已经确定了c,放在字符串常量池(堆中),但编译期static不执行的,a和b的值是未知的,
static代码块,在初始化的时候被执行,初始化属于类加载的一部分,属于运行时常量池(方法区中)。
运行时是这样的String s6=new StringBuilder().append(s3).append(s4).toString();这里的过程是通过StringBuilder这个类实现的
它是通过new String()的方式来作为值进行返回的,所以是在堆中开辟的一块空间。所以和常量池中的不一样

1,在java 中,直接使用==操作符,比较的是两个字符串的引用地址,并不是比较内容,比较内容请用String.equals()

2,程序运行时,除非手动向常量池中添加常量(比如调用intern方法),否则jvm不会自动添加常量到常量池。

整型常量池、浮点型常量池等

Byte,Short,Integer,Long,Character,Boolean都实现了常量池技术

数值类型的常量池不可以手动添加常量,程序启动时常量池中的常量就已经确定了,
    比如整型常量池中的常量范围:-128~127,
    Byte,Short,Integer,Long,Character,Boolean这5种包装类默认创建了数值[-128,127]的相应类型的缓存数据,
    但是超出此范围仍然会去创建新的对象

-128到127之 间的Integer会缓存到一个Integer数组中去了:
    如果你要把一个int变成一个Integer对象,首先去缓存中找,找到的话直接返回引用给你,不必再新new一个,
    如果不在-128-IntegerCache.high(127) 时会返回一个新new出来的Integer对象

Integer源码,里面有个静态类IntegerCache
它对Integer进行了缓存,范围是[-128,127],只要是这个范围内的数字都会缓存到这个里面,做成常量池进行管理


private static class IntegerCache {
    static final int high;
    static final Integer cache[];

    static {
        final int low = -128;
        // high value may be configured by property
        int h = 127;
        if (integerCacheHighPropValue != null) {
            // Use Long.decode here to avoid invoking methods that
            // require Integer's autoboxing cache to be initialized
            int i = Long.decode(integerCacheHighPropValue).intValue();
            i = Math.max(i, 127);
            // Maximum array size is Integer.MAX_VALUE
            h = Math.min(i, Integer.MAX_VALUE - -low);
        }
        high = h;
        cache = new Integer[(high - low) + 1];
        int j = low;
        for (int k = 0; k < cache.length; k++)
            cache[k] = new Integer(j++);
    }

    private IntegerCache() {
    }
}

字符串常量池(String Constant Pool)

存储编译期类中产生的字符串类型数据

JDK6.0及之前版本,字符串常量池是放在Perm Gen区(也就是方法区)中
JDK7.0版本,字符串常量池被移到了堆中了

运行时常量池和字符串常量池是独立的

String.intern()
检查字符串常量池中是否存在String并返回池里的字符串引用
若池中不存在,则将其加入池中,并返回其引用。 
这样做主要是为了避免在堆中不断地创建新的字符串对象

class常量池(Class Constant Pool)

每一个Java类被编译后,就会形成一份class文件

class文件中除了包含类的版本、字段、方法、接口等描述信息外

还有一项信息就是常量池(constant pool table),用于存放编译器生成的各种字面量(Literal)和符号引用(Symbolic References)

常量池_其他

运行时常量池(Runtime Constant Pool)

运行时常量池包含了类的运行时常量和静态方法等Class常量池的数据

JVM中运行时常量池在方法区中,是class常量池被加载到内存之后的版本

当类加载到内存中后,jvm就会将class常量池中的内容存放到运行时常量池中

运行时常量池和字符串常量池是独立的

new关键字

使用new关键字当然是每次都是新建一个,分配自己的空间