以下程序编译环境为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
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