扩展 - int 和 Integer
# int 和 Integer
int 是整型数字,是 Java 八大基本类型之一(Primitive types:short、int、long、char、byte、float、double、boolean)。Java 语言虽然号称一切都是对象,但原始数据类型是例外。
Integer 是 int 的包装类。它有一个 int 类型字段 value 存储数据,并且提供了基本操作,如运算(min、max、sam),int 和字符串之间的转换等。
在 Java 5 中,引入自动装箱和自动拆箱功能(boxing / unboxing),Java 可以根据上下文,自动进行装拆箱(int 到 Integer 和 Integer 到 int),极大简化了相关编程。
在 Java 5 进行了另一个改进: Integer 值缓存。通常构建 Integer 对象是调用构造函数 new Integer(1)
,直接 new 一个对象。但实际中发现大部分的数据操作都是集中在有限的、较小数值范围。因而 Java 5 中在 Integer 中新增了 valueOf() 静态方法,将某范围内的值缓存,之后直接从缓存中获取,无需创建新的对象。带来明显的性能改进。
由于存在缓存机制,所以在进行数值比较时可能出现:
Integer a1 = 100; // 会自动编译为:Integer.valueOf(100)
Integer a2 = Integer.valueOf(100);
a1 == a2; // 结果为 true;
Integer b1 = Integer.valueOf(200);
Integer b2 = Integer.valueOf(200);
b1 = b2; // 结果为 false
2
3
4
5
6
7
由于 == 只比较对象的引用地址,且 a1 和 a2 都引用相同地址,所以相等。正确做法应该使用 equals 进行数值比较,如 b1.equals(b2); 原理是将包装类拆箱为原始数据类型进行比较: value == ((Integer)obj).intValue()
扩展:
- 通过 valueOf() 方法获取 Integer 对象
public final class Integer extends Number implements Comparable<Integer> {
// 存储数据,final 不可变,保证了基本的信息安全和并发编程中的线程安全
private final int value;
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
}
2
3
4
5
6
7
8
9
10
该范围在 Integer 的内嵌类 IntegerCache 静态类中实现,默认为 -128 到 127,最大值可以通过虚拟机配置参数进行设置:
-XX:AutoBoxCacheMax=<size>
而 IntegerCache 缓存机制将在第一次调用时,在静态代码块中初始化。
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;
// 如果虚拟机有配置整数缓存最大值,则替换默认的 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() {}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
这种缓存机制通用存在于其他包装类:
- Boolean 缓存了 true/false 对应实例,确切说,只会返回两个常量实例 Boolean.TRUE/FALSE
- Short 缓存了 -128 到 127 数值
- Long 缓存了 -128 到 127 数值
- Byte 数值有限,缓存所有, -128 到 127 数值
- Character,缓存范围’\u0000’ 到 ‘\u007F’
自动拆箱和自动装箱
自动装箱是一种语法糖,虽然写法不同,但在编译阶段根据上下文自动进行了一些转换,即生成相同的字节码
Integer boxing = 1; // 实际会自动转换成 Integer.valueOf(1); int 自动装箱为 Integer 包装类 int unboxing = integer ++; // 实际会自动转换成 (integer ++).intValue(); Integer 自动拆箱为 int 基本类型
1
2需注意:在有性能要求的场合,避免无意中的装箱和拆箱。在性能极度敏感的场景一般会避免创建大量的对象以及提高处理速度,使用原始数据类型、数组来替换包装类和动态数组 ArrayList 等可以作为性能优化的备选项。例如:
// 线程安全计数器 class Counter { private final AtomicLong counter = new AtomicLong(); public void increase() { counter.incrementAndGet(); } }
1
2
3
4
5
6
7可以使用原始数据类型改写为:
class CompactCounter { private volatile long counter; private static final AtomicLongFieldUpdater<CompactCounter> updater = AtomicLongFieldUpdater.newUpdater(CompactCounter.class, "counter"); public void increase() { updater.incrementAndGet(this); } }
1
2
3
4
5
6
7如果原型数据类型计算时,需考虑线程安全,可使用 AtomicInteger 或 AtomicLong 等线程安全类。
Java 原始数据类型和引用类型局限性:原始数据类型和 Java 泛型并不能配合使用;无法高效地表达数据,也不便于表达复杂的数据结构,比如 vector 和 tuple;