[Java] 基本数据类型对常量池的使用

1 常量池是什么

.java文件会编译为.class文件,常量池指的是.class文件中一部分,可以理解为.class文件的资源仓库 [1] 。常量池中主要存放两类常量:

  • 字面量 (Literal)
    • 文本字符串、声明为final的常量值等
  • 符号引用 (Symbolic Reference)
    • 类和接口的全限定名 (Fully Qualified Name)
    • 字段(field)的名称和描述符(Descriptor)
    • 方法(method)的名称和描述符

在常量池中,所有类型的常量均存放在表结构中,但是不同的常量类型对应的表结构也不相同, 具体结构需要参考Java Language Specification。
在Java程序执行的过程中,上述常量池会加载到虚拟几种,成为运行时常量池(Runtime Constant Pool)。

1.1 基本数据类型的包装类对常量池的使用

Java程序中基本类型的变量可以直接在常量池中读取字面量。基本类型的包装类的大部分都实现了常量池技术,这些类是Byte,Short,Integer,Long,Character,Boolean。另外Byte,Short,Integer,Long,Character这5种整型的包装类只是在对应值小于等于127时才可使用常量池,见示例代码内的注释:

public class Test{

    Integer i1=new Integer(1);
    Integer i2=new Integer(1);
    //i1,i2对象具有不同的内存地址
    System.out.println(i1==i2);//输出false

    Integer i3=1;
    Integer i4=1;
    //i3,i4指向常量池中同一个字面值,即同一个内存位置,因此下面语句返回true
    System.out.println(i3==i4);//输出true

    //而i1,i3位于不同的内存位置,会返回false
    System.out.println(i1==i3);//输出false
}

但是如果上述代码中,int的值大于127或者小于-128,那么变量不会引用常量池中的字面量,而是创建新的变量。

Integer i1 = 1111;
Integer i2 = 1111;
//由于常量值对于127,不会使用常量池技术,因此输出false
System.out.println(i1==i2);
//但是两个变量的内容是相等的,因此输出true
System.out.println(i1.equals(i2));

2 模拟基本数据类型对常量池的使用

在编译过程中,编译器将Integer i = 5 之类的语句编译为Integer i = Integer.valueOf(5)。实际上,Java正是通过Iteger.valueOf(int i)方法实现了基本数据类型的自动装箱。同时,在valueOf()方法中使用常量池技术。代码模拟如下:

public static Integer valueOf(int i) {
    final int offset = 128;
    if (i >= -128 && i <= 127) { // must cache
        return IntegerCache.cache[i + offset];
    }
    return new Integer(i);
}

private static class IntegerCache {
    private IntegerCache(){};
    static final Integer cache[] = new Integer[-(-128) + 127 + 1];
    static {
        for(int i = 0; i < cache.length; i++){
            cache[i] = new Integer(i - 128);
        }
    }
}

上述代码中valueOf()方法首先判断数据是否小于127且大于-128,如果符合条件,那么直接返回已经缓存好的对象,这些对象在类加载时候由static语句块(一般用于初始化静态变量)初始化。可以看出,相同字面值的对象具有相同的内存地址。

参考文献

[1] 周志明. 深入理解Java虚拟机[M]. 机械工业出版社, 2011.

你可能感兴趣的:(Java)