神奇的常量池和intern方法

时间:2022-12-29 17:22:47

以下程序编译环境为JDK1.8,且是对于复合数据类型而言,不针对原始数据类型如byte\int\short\float\double等

一、引入

我们也许会被灌输一个观念,就是复合数据类型比较要用equals,不能用==,否则会出现值相等和地址不相等导致错误。
让我们思考网上的两个有趣的例子

/***** exmple1 ******/
Integer i1 = 1;
Integer i2 = 1;
System.out.println(i1 == i2);//true

Integer i3 = 100;
Integer i4 = 100;
System.out.println(i3 == i4);//true

Integer i5 = 127;
Integer i6 = 127;
System.out.println(i5 == i6);//true

Integer i5n = new Integer(127);
Integer i6n = new Integer(127);
System.out.println(i5n == i6n);//false

Integer i7 = 128;
Integer i8 = 128;
System.out.println(i7 == i8);//false

Integer i9 = 1000;
Integer i10 = 1000;
System.out.println(i9 == i10);//false


/***** exmple2 比较上下两段代码******/
// String str1 = new String("1") + new String("1");
// System.out.println(str1.intern() == str1); //true

String str1 = new String("1") ;
System.out.println(str1.intern() == str1); //false
System.out.println(str1 == "1"); //false

这里用了==比较,但也许疑惑,为什么的1、100、127会有==,而new、128、1000会不==,这是偶然还是必然;为什么example2的上下两段代码的运行结果会不一样,难道受”+”影响了?intern()返回值是什么……

二、intern()和常量池

intern()

对于JDK7以上的string调用intern方法,会string的值与常量池比对,若不存在则创建常量池变量,无论成功与否都会返回常量池的引用。

常量池

常量池,顾名思义,就是将对象放入池中,使用时通过对象引用的方式。
好处是避免频繁的创建和销毁对象而影响系统性能,其实现了对象的共享。
那我们在编程中如何使用常量池呢?

  • public final static
    我们也许用过这个声明过全局静态变量

  • 对象创建方式

  String str1 = "abcd";
String str2 = new String("abcd");
System.out.println(str1==str2);//false

第一种是,在常量池中寻找 “abcd”,若有则返回引用,若无则创建常量池对象并返回引用;第二种方式是直接在堆内存空间创建一个新的对象。所以str1和str2会指向不同的地址
若改为下面

  String str1 = "abcd";
String str2 = "abcd";
System.out.println(str1==str2);//true

因为str2在常量池中会找到”abcd”

  • 连接表达式 +
String str1 = "cunteng";
String str2 = "008";

String str3 = "cunteng" + "008";
String str4 = str1 + str2;
System.out.println(str3 == str4);//false

String str5 = "cunteng008";
System.out.println(str3 == str5);//true

原因是常量和常量相加为常量,二变量str1+str2在编译时期是无法确定的,所以str4不会到常量池检查是否罕有该常量
注意特例: public static final 创建的常量与文本创建的
如”cunteng”是一样的

public class Main {
final static String str1 = "cunteng";
final static String str2 = "008";
public static void main(String[] args) throws Exception {
String str3 = "cunteng" + "008";
String str4 = (str1 + str2);
System.out.println(str3 == str4);//true

String str5 = "cunteng008";
System.out.println(str3 == str5);//true

}

}
  • String s1 = new String(“cunteng008”);发生了什么??
    每次new String(“cunteng008”),都会产生一个新的对象;且将”cunteng008”常量池的对象比较,若不存在,则在常量池创建新对象,我觉得它相当于暗中进行了一次”cunteng008”.intern();
    看一个有趣的例子
/** example3 **/
String str1 = new String("cunteng") + new String("008");
System.out.println(str1.intern() == str1); //true

/** example4 **/
String str1 = new String("cunteng") ;
System.out.println(str1.intern() == str1); //false

对于example3,new String(“cunteng”)和new String(“008”)都会分别产生一个对象,并将”cunteng”和”008”放入常量池中;但 new String(“cunteng”)整体并不是一个常量,两个这样的对象会产生一个新的对象;当str1.intern()时,常量池中只有”cunteng”和”008”,并没有”cunteng008”,所以常量池会新增”cunteng008”,但不会新创建对象,而是直接引用str1;
new String(“cunteng”)后,常量池已经有了”cunteng”,是常量池新创建的,所以与str1指向自然不同。

  • 使用了常量池的基本类型包装类
    • Integer
      -128 ~ 127 装箱时使用了常量池,不会产生新的对象,二超范围的会。
Integer i = A; //A为常量
等价于
Integer i = Integer.valueOf(A);
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
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() {}
}
  • Bool
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
/**
* The {@code Boolean} object corresponding to the primitive
* value {@code true}.
*/

public static final Boolean TRUE = new Boolean(true);
/**
* The {@code Boolean} object corresponding to the primitive
* value {@code false}.
*/

public static final Boolean FALSE = new Boolean(false);

三、总结

  • intern返回的是常量池对象引用
  • new会生成一个新的对象,分配新的内存;
  • 常量相加等于常量,会加入到常量池;
  • 已经创建了的对象,在调用intern时,常量池不会创建新的对象,而是直接指向已创建的对象;
  • final static 为常量;
  • 基本类的包装类有部分使用常量池技术

在看看引入的问题,就简单了许多

/***** exmple1 ******/
/*
1/100/127在[-128,127]范围内,引用常量池,故==;128/1000超出会分别创建新的对象,故不==;new对象一定会创建新对象,故不==。
*/

Integer i1 = 1;
Integer i2 = 1;
System.out.println(i1 == i2);//true

Integer i3 = 100;
Integer i4 = 100;
System.out.println(i3 == i4);//true

Integer i5 = 127;
Integer i6 = 127;
System.out.println(i5 == i6);//true

Integer i5n = new Integer(127);
Integer i6n = new Integer(127);
System.out.println(i5n == i6n);//false

Integer i7 = 128;
Integer i8 = 128;
System.out.println(i7 == i8);//false

Integer i9 = 1000;
Integer i10 = 1000;
System.out.println(i9 == i10);//false


/***** exmple2 比较上下两段代码******/
/*
str1不会进入常量池,当str1.intern时,因为str1已创建对象,故常量池直接指向str1;
new String("1")后常量池创建新对象并存下"1",常量池的对象与str1不同,故调用intern时返回的常量池指向的对象与str1不==;
*/

// String str1 = new String("1") + new String("1");
// System.out.println(str1.intern() == str1); //true

String str1 = new String("1") ;
System.out.println(str1.intern() == str1); //false
System.out.println(str1 == "1"); //false

参考
Java常量池理解与总结
Java面试——从JVM角度比较equals和==的区别
Java技术——你真的了解String类的intern()方法吗
IntegerCache in Java