Integer.valueOf() 的享元模式应用


在 Java 中,如果我们想要创建一个 Integer 对象,一般有以下三种方式:

  1. 通过标准对象创建语法 new Interger(int) 来创建

  2. 通过静态工厂 Integer.valueOf(int) 来创建

  3. 通过自动装箱将基本类型 int 自动转换为包装类型 Integer,这其实是通过对 Integer.valueOf() 的自动调用来完成的

方式 1 和方式 3 本质上没有什么区别,只不过方式 1 看起来更简洁,那么方式 1 和方式 2 有什么区别呢?先来看一个经典的面试题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void main(String[] args) { 
Integer m = Integer.valueOf(111);
Integer n = Integer.valueOf(111);
System.out.println(m == n);

m = new Integer(111);
n = new Integer(111);
System.out.println(m == n);

Integer i = Integer.valueOf(222);
Integer j = Integer.valueOf(222);
System.out.println(i == j);

i = new Integer(222);
j = new Integer(222);
System.out.println(i == j);
}

大家可以不急着看答案,先独立思考一下以上的打印结果。我想刷过面试题的同学答对这道题应该问题不大,并且能够知道一些原因。但是真正深入了解源码,以及背后设计思想的同学应该也不是很多。我们先来看看构造函数 Interger(int) 的源码(JDK 17),如下所示:

Integer.java 🔗
1
2
3
4
5
private final int value;
@Deprecated(since="9", forRemoval = true)
public Integer(int value) {
this.value = value;
}

使用 new 关键字创建对象会自动调用该对象的相应构造函数,从以上源码中可以看出,Interger(int) 构造函数只做一件事,那就是初始化成员常量 value 的值,该常量代表了 Integer 对象的值。

使用 new 关键字每次都会在堆内存上开辟一块新的空间用于存放创建出来的对象,只要使用了 new 关键字那么创建出来的对象就是新的对象,而 == 关系操作符比较的是两个对象之间的引用是否相等(即是否引用了同一个对象),所以使用 new 创建的每一个 Integer 对象使用== 关系操作符得到的结果都是false

我们再来看看静态工厂 Integer.valueOf(int) 的源码(JDK 17),如下所示:

Integer.java 🔗
1
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
36
37
38
39
40
41
42
43
44
45
46
47
48
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;
String integerCacheHighPropValue =
VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
h = Math.max(parseInt(integerCacheHighPropValue), 127);
// Maximum array size is Integer.MAX_VALUE
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
CDS.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() {}
}
@IntrinsicCandidate
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}

从以上源码可以看出,Integer 类中包含一个私有的静态类 IntegerCache,该类是一个 Integer 对象数组(默认 -128 ~ 127)的缓存,缓存在第一次使用时被初始化,缓存的大小(Integer 缓存对象的最大值)可以由 JVM 参数-XX:AutoBoxCacheMax=<size> 或系统属性-Djava.lang.Integer.IntegerCache.high=<size>来设置。

如果我们通过静态工厂 Integer.valueOf(int) 来创建 Integer 对象,首先会判断创建的对象值是否在 IntegerCache 中有缓存,有的话直接取缓存中的值,否则通过标准对象创建语法 new Interger(int) 创建并返回。

所以默认情况下,如果使用 Integer.valueOf(int) 创建的 Integer 对象值在 -128 ~ 127 之间,那么无论创建多少次,创建的每一个对象使用== 关系操作符得到的结果都是true,否则都是false

注:如果修改过java.lang.Integer.IntegerCache.high属性值,那么结果就另当别论了。

静态工厂 Integer.valueOf(int) 的设计正是对享元模式的应用。享元模式定义如下:

享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。

享元模式尝试重用现有的同类对象,如果未找到匹配的对象,则创建新对象。

Integer.valueOf(int) 通过享元模式缓存频繁请求的值来显着提高空间和时间性能,效率远远高于通过 new Integer() 创建对象的方式。所以 Java 9 中直接弃用了new Integer(),我们以后在创建 Integer 的包装类型时,也尽量不要再使用 new Integer() 的方式了。

注:其它包装类型(Long、Short 等)也类似。