学习和使用java已经有4年多了,反省一下,太多关注了实际的应用层面,对某些基础的东西没有特别理解透彻。好记性不如烂笔头,记录下一些基础的知识点,把基础夯实,才能更进一步。
先引用一下java常量池的说明:
常量池在java用于保存在编译期已确定的,已编译的class文件中的一份数据。它包括了关于类,方法,接口等中的常量,也包括字符串常量,如String s = “java”这种申明方式;当然也可扩充,执行器产生的常量也会放入常量池,故认为常量池是JVM的一块特殊的内存空间。
java是一种动态链接的语言,常量池的作用非常重要,常量池中除了包含代码中所定义的各种基本类型(如int、long等等)和对象型(如String及数组)的常量值外,还包含一些以文本形式出现的符号引用,比如:
类和接口的全限定名;字段的名称和描述符;方法的名称和描述符。
对于程序员来说,这样的说明略显枯燥和抽象,我们还是用代码来说明一切:
Integer i1 = new Integer(1);
Integer i2 = new Integer(1);
System.out.println(i1 == i2); // false
Integer i3 = 1;
Integer i4 = 1;
System.out.println(i3 == i4); // true
对于上面的i1和i2指向的对象,都是用new关键字来创建的,那么2个实际的对象都创建在堆内存上,就是说,i1和i2分别指向了堆内存上的2个地址,那么用==来比较的时候,肯定返回false了。对于i3和i4,是直接指定了值,这时候,java虚拟机会先到常量池里面去找,如果找到了,就返回对应的引用;如果没找到,那么就重新在堆内存上创建,类似于new。
那么,对于上面的Integer,多大的范围内数会在常量池中存在呢?我们打开Integer类的源码,发现它其实是使用了一个内部类IntegerCache来实现这个功能的:
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
}
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() {}
}
不难看出来,当数值在-128—127的时候,会从常量池中取得。当然,我们自己写代码验证一下,会更好:
Integer i5 = 127;
Integer i6 = 127;
System.out.println(i5 == i6); // true
Integer i7 = 128;
Integer i8 = 128;
System.out.println(i7 == i8); // false
类似的,其他几种基本数据类型和对应的封装类类都有使用常量池,不过,float和double没有,其实也比较容易理解,如果这2中在常量池中要有,那要放哪些值呢?如果是Integer,那么-128—127明显比其他的值更加常用,把这些放在常量池中比较合理,但是对于浮点数,就没有明显证据表明哪几个更加常用了。
其实除了基本数据类型之外,还有一种对象在java中很常用,也会用到常量池,就是String。提到String类和常量池,必须提到String的一个native方法:intern()。intern方法会返回String对象的在常量池中的引用,如果在常量池中已经存在这个字符串了,那么直接返回这个引用;如果没有,就先把这个字符串复制到常量池,然后返回引用。依然通过代码来说明:
String s1 = new String("java");
String s2 = "java";
System.out.println(s1 == s2); // false
System.out.println(s1.intern() == s2); // true
备注:
1、以上的源码都来自JDK1.7,不同版本的JDK可能会有不同。
2、运行环境为java自带的hotspot虚拟机,如果执行其他虚拟机,由于实现机理可能不同,运行结果也可能不同。