Java语言虽然号称一切都是对象,但是原始数据类型除外。Java有8个原始数据类型(primitive type),那就是boolean,int,short,long,char,float,double,byte。Integer是int的包装类,内部有一个private final int value,并且内部提供了基本操作,比如数学运算,int和字符串之间的转换。更重要的是从Java 5中引用了自动装箱/自动拆箱(boxing/unboxing)的操作,Java可以根据上下文,自动的进行转换,极大的简化了相关编程。关于Integer值的缓存,这涉及到Java 5另外一个改进。构建Integer对象的传统方式是直接new一个对象,但是要注意的是new一个对象并没有用到Integer值的缓存。根据实践,我们发现大部分的数据操作都是集中在一个有限的,较小的数值范围内。所以Java 5中新增了静态工厂方法valueOf()方法,在调用这个静态工厂方法的时候,会利用一个缓存机制,带来了明显的性能改进。根据Java Doc,这个缓存的范围在-128~127。
既然这里说到Integer的缓存机制,就展开一下。下面是Integer的源码中关于缓存的实现代码
//从这里可以看出调用valueOf的时候,首先会先判断要转化的i是否在缓存的范围内,也就是是否
//在low和high之间,如果在,并不用直接创建对象,而是从cache这个数组中取值
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 low = -128;
static final int high;
static final Integer[] cache;
static Integer[] archivedCache;
static {
// high value may be configured by property
int h = 127;
//从JVM参数中获取参数high
String integerCacheHighPropValue =
VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
//比较获取的参数值和127比较,确保最后设置的值是最大的
h = Math.max(parseInt(integerCacheHighPropValue), 127);
// Maximum array size is Integer.MAX_VALUE
//public static final int MAX_VALUE = 0x7fffffff;
//我在Eclipse中打印出2147483647,也就是说h最大是0x7ffffffff+127
h = Math.min(h, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
// Load IntegerCache.archivedCache from archive, if possible
VM.initializeFromArchive(IntegerCache.class);
int size = (high - low) + 1;
// Use the archived cache if it exists and is large enough
if (archivedCache == null || size > archivedCache.length) {
Integer[] c = new Integer[size];
int j = low;
for(int i = 0; i < c.length; i++) {
c[i] = new Integer(j++);
}
archivedCache = c;
}
cache = archivedCache;
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
接下来我们理解一下Java的自动装箱和自动拆箱行为:
自动装箱实际上算是一种语法糖,可以简单理解为Java平台为我们自动进行了一些转换,保证不同的写法在运行时的结果是一致的,也就是说装箱发生在编译阶段,生成的字节码文件是一样的。Javac替我们把自动装箱替换为Integer.valueOf(),自动拆箱替换为integer.intValue()。这中缓存机制并不是Integer特有的,而是包装类都有这种机制。比如:
Boolean缓存了true/false对应的实例,确切的说只会返回两个常量实例Boolean.TRUE/Boolean.FALSE。
Short同样是缓存了-128~127的数值
Byte因为数值有限,所以全都被缓存
Character缓存范围是'\u0000'到'\u007F'
自动装箱和自动拆箱看起来很牛逼的样子,在编程实践中,我们有什么要注意的吗?
原则上应该避免无意中的自动装箱/自动拆箱的行为,尤其是在性能敏感的场合,创建10万个java对象和10万个int类型花费的开销并不是一个数量级的,不管是内存使用还是处理速度,光是对象头的内存空间就已经是数量级的差距了。
这里要扩展一下对象的内存结构:
对象在内存的存储结构分为三部分:对象头(object head),实例数据(instance data),填充数据(padding)。
更加详细的内容,请参考这篇博客 https://www.jianshu.com/p/0a63a4d22765
最后思考一下原始数据类型是线程安全的吗?
字符串是不可变的,Integer类的属性value被定义成final类型,同样是不可变的,而是还是private属性,这样就保证了基本的信息安全和并发编程中的线程安全。但是对于原始数据类型,显然我们要使用并发相关手段才能保证线程安全。如果有线程安全的计算需要,建议使用类似的AtomicInteger, AtomicLong这样的线程安全类。