一道经典面试题:
public class Main {
private Integer a = 123;
private Integer b = 128;
private Long c = 123L;
private Long d = 1000L;
String str = "abc";
public static void main(String[] args) {
Main m = new Main();
System.out.println(Integer.valueOf(123)==m.a);
System.out.println(Integer.valueOf(128)==m.b);
System.out.println(Long.valueOf(123)==m.c);
System.out.println(Long.valueOf(1000)==m.d);
System.out.println(new String("abc").intern()==m.str);
System.out.println(new String("ab").intern()==m.str);
}
}
如上的结果中,程序的输出结果依次是什么?
结果:
true
false
true
false
true
false
通常,我们知道这是因为缓存的问题,那么为什么会有缓存,为什么缓存的大小是这样的,缓存的大小可不可以人为设置呢?
可更改的IntegerCache
我们先来看Integer,查看Integer类中的valueOf方法:
/**
*返回表示指定的表示int值的Integer实例。如果没有新的Integer实例
*要求,一般应在构造函数Integer(int)之前优先使用此方法,因为这
*种方法会缓存经常请求的值来提高显着更好的空间和时间性能
*
*
*此方法将始终缓存-128到127范围内的值,
*包含,并可以缓存此范围之外的其他值。
*
* @param我是一个{@code int}值。
* @return表示{@code i}的{@code Integer}实例。
* @since 1.5
*/
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
如上说明,如果定义的是Integer类型的变量默认情况下只能缓存-128到127之间的值,但是如注释所说,它也可以缓存除此范围之外的值。也就是说实际上这个范围并不是固定的。而IntegerCache是Integer的内部类,它的定义如下:
/**
*缓存以支持自动装箱之间的值的对象标识语义
* JLS要求的-128和127(含)。
*
*首次使用时初始化缓存。 缓存的大小
*可能由{@code -XX:AutoBoxCacheMax = <size>}选项控制。
*在VM初始化期间,java.lang.Integer.IntegerCache.high属性
*可以设置并保存在私有系统属性中
* sun.misc.VM类。
*/
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) {
try {
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);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
通过源码我们了解到,默认范围为[-128,127],但是IntegerCache可以通过获取jvm中已保存的属性java.lang.Integer.IntegerCache.high来设置缓存的大小,设我们的设定值为m,则Integer的缓存大小为[-128,m],m最大不应超过Integer.MAX_VALUE+low(负数),我们可以使用jvm参数“-XX:AutoBoxCacheMax = <size>”控制自动装箱时的缓存大小。
举个栗子,如下代码:
代码1:
public class Main {
public static void main(String[] args) {
Integer a = 128;
System.out.println(Integer.valueOf(128)==a);
}
}
默认情况下上述代码的结果是false,但是我们如果手动修改自动装箱的缓存大小(-128~200)如下:
此时我们再运行,结果就变为true了。
另外我们还有可能会把下面代码的输出结果弄错。。。
代码2:
public class Main {
public static void main(String[] args) {
int a = 128;
System.out.println(Integer.valueOf(128)==a);
}
}
输出结果会是什么呢?请不要犯了关心思维的毛病,就直觉的认为这个输出的也是false;但是这个程序正确的输出结果是true。我们可以通过反编译生成两者的字节码进行分析:
代码1的:
0: sipush 128
3: invokestatic #16 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
6: astore_1
7: getstatic #22 // Field java/lang/System.out:Ljava/io/PrintStream;
10: sipush 128
13: invokestatic #16 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
16: aload_1
17: if_acmpne 24
20: iconst_1
21: goto 25
24: iconst_0
25: invokevirtual #28 // Method java/io/PrintStream.println:(Z)V
28: return
可以看到代码1中,Integer a=128这时候先调用了Integer.valueOf方法,然而由于此时的值不在缓存范围内,所以只好调用构造函数生成一个对象实例,之后再次调用Integer.valueOf()又生成了一个对象实例,所以这两个都不在缓存中,比较的是对象实例,所以不相等。
代码2的:
0: sipush 128
3: istore_1
4: getstatic #16 // Field java/lang/System.out:Ljava/io/PrintStream;
7: sipush 128
10: invokestatic #22 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
13: invokevirtual #28 // Method java/lang/Integer.intValue:()I
16: iload_1
17: if_icmpne 24
20: iconst_1
21: goto 25
24: iconst_0
25: invokevirtual #32 // Method java/io/PrintStream.println:(Z)V
可以看到这里没有调用valueOf方法,而是直接将常量加载到操作数栈后,将其存储到局部变量表中。然后在生成Integer对象后调用intValue方法进行值的比较,即该等式相当于Integer.valueOf(128).intValue()==128,所以返回的结果是true。
LongCache可更改吗?
正如我们之前修改Integer自动装箱的缓存大小一样,我们自然会问:LongCache的大小能更改吗?
别急,我们下来看下源码:
private static class LongCache {
private LongCache(){}
static final Long cache[] = new Long[-(-128) + 127 + 1];
static {
for(int i = 0; i < cache.length; i++)
cache[i] = new Long(i - 128);
}
}
/**
* 返回指定的表示long数值的实例,如果不需要新的实例
* 则将此方法优先于构造函数使用,因为这种方法会
* 通过缓存显着改善空间和时间性能
* 请注意,与Integer中的valueOf(int)方法不同
* ,此方法不能够指定缓存特定内容中的取值范围。
*
* @param l a long value.
* @return a {@code Long} instance representing {@code l}.
* @since 1.5
*/
public static Long valueOf(long l) {
final int offset = 128;
if (l >= -128 && l <= 127) { // will cache
return LongCache.cache[(int)l + offset];
}
return new Long(l);
}
可以看到,LongCache的实现是通过静态数组实现的,它的大小是固定不能改变的,所以只能是-128~127之间,同样的Short类型的也是如此。
迷人的intern方法
javaAPI中对string中的intern方法解释如下:
public String intern()
返回字符串对象的规范化表示形式。
一个初始为空的字符串池,它由类 String
私有地维护。
当调用 intern 方法时,如果池已经包含一个等于此 String
对象的字符串(用 equals(Object)
方法确定),则返回池中的字符串。否则,将此 String
对象添加到池中,并返回此 String
对象的引用。
它遵循以下规则:对于任意两个字符串 s
和 t
,当且仅当 s.equals(t)
为 true
时,s.intern() == t.intern()
才为 true
。
所有字面值字符串和字符串赋值常量表达式都使用 intern 方法进行操作。
所以当我们输入以下代码时:
public class Main {
public static void main(String[] args) {
String s = "abc";
System.out.println(new String("abc").intern()==new String("abc"));
System.out.println(new String("abc").intern()=="abc");
System.out.println(new String("abc").intern()==s.intern());
}
}
第一个由于常量池中的引用与对象的hashcode不同,所以导致不相等。而后两个常量自身的比较,所以他们的结果是相等的。正确结果为false,true,true