java是从1.5 才引入的基础数据类型的自动拆装箱操作。本文主要基于java1.8的测试结果及源码跟踪来对一些知识点进行说明。
jvm规则
在《深入理解java虚拟机》中,有提到几点关于jvm对于自动拆箱的说明:
- 全等运算符
==
在遇到算术运算符的情况下会自动拆箱,否则,不会自动拆箱 - 包装类的
equals
方法基本都有重写,有严格的类型校验要求
全等规则
-
全等运算符
==
如果是基本数据类型,则比较值大小是否相等; 如果是引用类型,则比较引用对象的内存地址是否相同
-
equals方法
如果重写,则以重写的逻辑为准; 如果不重写(不考虑中间父类重写),则以Object对象的equals为准 Object的equals方法采用的逻辑是全等运算符。
特殊情况
数据缓存机制
先看一个测试Case
Integer a = 2;
Integer b = 2;
Integer x = 128;
Integer y = 128;
Float f1 = 127f;
Float f2 = 127f;
System.out.println("a==b ? " + (a==b));
System.out.println("x==y ? " + (x==y));
System.out.println("f1==f2 ? " + (f1 == f2));
运行该段代码,输出结果为:
a==b ? true
x==y ? false
f1==f2 ? false
之所以会出现这种结果,主要是跟包装类自身的实现有关。
短整型(int)和长整形(long)有全局缓存,而浮点型则没有,该缓存大小为1个字节,针对有符号整数,也就是说[-128,127]
范围内的整形包装类都指向同一个缓存实例对象,这从源码中也可以看出。
Integer缓存逻辑
/**
* Cache to support the object identity semantics of autoboxing for values between
* -128 and 127 (inclusive) as required by JLS.
*
* The cache is initialized on first usage. The size of the cache
* may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.
* During VM initialization, java.lang.Integer.IntegerCache.high property
* may be set and saved in the private system properties in the
* sun.misc.VM class.
*/
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() {}
}
Long缓存逻辑
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);
}
}
所以才有前面的测试代码的输出结果。
自动拆箱
对于有算术运算符(加减乘除等)参与的==
逻辑,包装类会进行自动拆箱,转换成对应的基础数据类型。对比以下测试Case
Integer a = 1;
Integer x = 128;
Integer y = 129;
System.out.println("(x + a) == y ? " + ((x + a) == y));
运行该段代码,输出结果为:
(x + a)== y ? true
可以看出,这里就对Integer
进行了自动拆箱为int
,比较时,是比较的值。
方法区常量池
java中的字符串类String
是一个比较核心的类。使用频率非常高。jvm对字符串也做了一些优化,主要是常量池的的相关实现(除了String
类型常量,还有基础数据类型和类元数据也会存储在方法区)。
jvm虚拟机会将java(仅考虑java语言)代码中的字面量(字符串常量、基础数据类型,包括成员变量以及局部变量)缓存到方法区的常量池中。如果通过直接赋值来获取对象实例时,会先在常量池中查找是否已经存在对应的值对象,如果存在,则将常量池中对应的值对象的引用返回。
特殊的,String
类的intern
方法。该方法会在即使显示的调用了new
关键字来分配新的内存时,仍然会先在常量池中进行值查找。
下面通过一个测试Case来验证一下。
String ss1 = "this is str";
String ss2 = "this is str";
String ss3 = new String("this is str");
String ss4 = new String("this is str").intern();
System.out.println("ss2 == ss1 ? " + (ss2 == ss1));
System.out.println("ss3 == ss1 ? " + (ss3 == ss1));
System.out.println("ss4 == ss1 ? " + (ss4 == ss1));
运行该段代码,输出结果为:
ss2 == ss1 ? true
ss3 == ss1 ? false
ss4 == ss1 ? true
我们也可以看下String. intern()
的源码说明
/**
* Returns a canonical representation for the string object.
* <p>
* A pool of strings, initially empty, is maintained privately by the
* class {@code String}.
* <p>
* When the intern method is invoked, if the pool already contains a
* string equal to this {@code String} object as determined by
* the {@link #equals(Object)} method, then the string from the pool is
* returned. Otherwise, this {@code String} object is added to the
* pool and a reference to this {@code String} object is returned.
* <p>
* It follows that for any two strings {@code s} and {@code t},
* {@code s.intern() == t.intern()} is {@code true}
* if and only if {@code s.equals(t)} is {@code true}.
* <p>
* All literal strings and string-valued constant expressions are
* interned. String literals are defined in section 3.10.5 of the
* <cite>The Java™ Language Specification</cite>.
*
* @return a string that has the same contents as this string, but is
* guaranteed to be from a pool of unique strings.
*/
public native String intern();
从中还可以看到对intern
后的字符进行比较时,如果结果为true
,则两者通过equals
比较的结果也必定为true
小结
虽然本文是对这些jvm以及源码中的优化手段进行了分析,证明了测试case的的正确性,但是在实际开发中,仍然不建议开发者进行这样的编码,不但会降低代码的可读性,还会增加逻辑上的迷惑性。
最后,遇到比较疑惑的问题时,不妨从源码以及jvm虚拟机规范中查找解决方案。