Java常量池总结

Java常量池总结

    一、基本概念介绍

    什么是常量

    用final修饰的变量即为常量(成员变量、局部变量、静态变量,只要被final给修饰过的)。

    

    什么是常量池

    先说静态常量池,来看看class文件中都包含了哪些内容。
    其中前四个字节为魔数(确定一个class文件能否被JVM解析),后四个字节为存储版本号,其中前两个字节为次版本号,后两个为主版本号,最后就是静态常量池,其中又包含字面量,和符号引用,字面量指的就是一些字符串和声明为final类型的常量,符号引用指的是字符串在常量池的引用,包括类和接口的全限定名(如:com/test/Test),方法以及属性的名称和描述符。
    以上就是class文件的内容,运行时常量池为方法区的一部分,上述的class文件中除了有class的类信息,方法字段信息,超类以及接口信息,还有静态常量池,在方法区中他会被加载到一块叫做运行时常量池的地方。
    

    二、主角运行时常量池    

    运行时常量池用于存放class文件中的常量池内容,也就是常量和方法以及字段信息,但这些都是编译期就有的内容,显然的,他的作用不仅于此。
    并非只有编译期class文件中的常量池的内容才能入住运行时常量池,在运行时,同样也可以通过String类的intern方法,可以显式地将新的值放进常量池。

    运行时常量池的好处:

    1、节省内存,如字符串,可以无需频繁地新建内存或者销毁内存,直接在常量池共享一个字符串;

    2、节省操作时间,通过==比较字符串是否相同效率显然是优于equals方法的,如果是常量池中同一个内容的引用,就无需进行equals比较了。

    三、运行时常量池的作用

    Java共有八种基础数据类型,都有各自的封装类,基本上都有对常量池的利用。

    其中除了Float和Double,其他的六种,byte,short,int,long,boolean,char大多对常量池有利用。

    在-128到127这个区间的数,会先使用常量池中的缓存内容,大于这个区间则需要新建一个对象,源码如下:

//Integer 缓存代码 :
public static Integer valueOf(int i) {
        assert IntegerCache.high >= 127;
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }
    再拿Integer做个例子:
  Integer i1 = 40;
  Integer i2 = 40;
  System.out.println(i1==i2);//输出TRUE

    由于40<127,所以两者都是指向了常量池中的缓存,故,输出true。

    注:如果显式地去new一个Integer对象,就会直接在堆中新建一个对象了。

    以下为更为丰富的一个例子:

  Integer i1 = 40;
  Integer i2 = 40;
  Integer i3 = 0;
  Integer i4 = new Integer(40);
  Integer i5 = new Integer(40);
  Integer i6 = new Integer(0);
  
  System.out.println("i1=i2   " + (i1 == i2));
  System.out.println("i1=i2+i3   " + (i1 == i2 + i3));
  System.out.println("i1=i4   " + (i1 == i4));
  System.out.println("i4=i5   " + (i4 == i5));
  System.out.println("i4=i5+i6   " + (i4 == i5 + i6));   
  System.out.println("40=i5+i6   " + (40 == i5 + i6));
    结果:
i1=i2   true
i1=i2+i3   true
i1=i4   false
i4=i5   false
i4=i5+i6   true
40=i5+i6   true
    总结:只要使用+或者显式地写一个等号赋值,默认的,都会去常量池中找内容。

    四、String类和常量池的关系

    String有两种创建方式,一种是直接等号赋值,一种是new String("***")。

    两者还是有本质上的区别的。

    第一种是在常量池中创建对象,一种是在堆中创建对象。

    使用+也可以生成String对象,如果加号中有String对象的引用而不是引号文本,则生成的String对象不会被加入常量池,如果加号中全部是引号文本,生成的对象会被加入常量池。

    也有特例:

    1、特例1:

public static final String A = "ab"; // 常量A
public static final String B = "cd"; // 常量B
public static void main(String[] args) {
     String s = A + B;  // 将两个常量用+连接对s进行初始化 
     String t = "abcd";   
    if (s == t) {   
         System.out.println("s等于t,它们是同一个对象");   
     } else {   
         System.out.println("s不等于t,它们不是同一个对象");   
     }   
 } 
s等于t,它们是同一个对象

    由于在编译期A和B就已经固定值了,s=A+B,效果等同于s="ab"+"cd"。

    2、特例2:

public static final String A; // 常量A
public static final String B;    // 常量B
static {   
     A = "ab";   
     B = "cd";   
 }   
 public static void main(String[] args) {   
    // 将两个常量用+连接对s进行初始化   
     String s = A + B;   
     String t = "abcd";   
    if (s == t) {   
         System.out.println("s等于t,它们是同一个对象");   
     } else {   
         System.out.println("s不等于t,它们不是同一个对象");   
     }   
 } 
s不等于t,它们不是同一个对象
    由于A和B的值,并不是在编译期就固定。

    说说创建了几个对象的问题:

    String s1 = new String("xyz"); **创建了几个对象? **
    考虑类加载阶段和实际执行时。
    (1)类加载对一个类只会进行一次。"xyz"在类加载时就已经创建并驻留了(如果该类被加载之前已经有"xyz"字符串被驻留过则不需要重复创建用于驻留的"xyz"实例)。驻留的字符串是放在全局共享的字符串常量池中的。
    (2)在这段代码后续被运行的时候,"xyz"字面量对应的String实例已经固定了,不会再被重复创建。所以这段代码将常量池中的对象复制一份放到heap中,并且把heap中的这个对象的引用交给s1 持有。
    这条语句创建了2个对象

    Intern方法和更加丰富的String例子

    运行期可以通过intern方法将String对象放入运行时常量池中,看以下的几个例子:

public class Test {
 public static void main(String[] args) {   
      String hello = "Hello", lo = "lo";
      System.out.println((hello == "Hello") + " ");
      System.out.println((Other.hello == hello) + " ");
      System.out.println((other.Other.hello == hello) + " ");
      System.out.println((hello == ("Hel"+"lo")) + " ");
      System.out.println((hello == ("Hel"+lo)) + " ");
      System.out.println(hello == ("Hel"+lo).intern());
 }   
}
class Other { static String hello = "Hello"; }
package other;
public class Other { public static String hello = "Hello"; }
    结果:
true true true true false true```
在同包同类下,引用自同一String对象.
在同包不同类下,引用自同一String对象.
在不同包不同类下,依然引用自同一String对象.
在编译成.class时能够识别为同一字符串的,自动优化成常量,引用自同一String对象.
在运行时创建的字符串具有独立的内存地址,所以不引用自同一String对象.

    五、不同JDK版本下的intern方法

    简言之,在JDK1.6中,当常量池中没有对象,intern方法会直接在常量池创建对象,并返回常量池的引用。

    而在JDK1.7以后,intern如果在常量池中发现了字符串,还是和JDK1.6一样处理,不同的是,当常量池中没有字符串的时候,就不会再去把堆中的字符串拷贝到常量池中了,而是将堆的引用拷贝到常量池。

    注:JDK1.6以前的常量池在方法区,而JDK1.6以后的常量池在堆上。

    可以看这个链接:传送门


    

猜你喜欢

转载自blog.csdn.net/that_is_cool/article/details/80548682