陷阱 - 过度使用原始包装类型效率低下
考虑这两段代码:
int a = 1000;
int b = a + 1;
和
Integer a = 1000;
Integer b = a + 1;
问题:哪个版本更有效?
答:这两个版本看起来几乎相同,但第一个版本比第二个版本效率更高。
第二个版本使用表示使用更多空间的数字,并依赖于幕后自动装箱和自动拆箱。实际上第二个版本直接等同于以下代码:
Integer a = Integer.valueOf(1000); // box 1000
Integer b = Integer.valueOf(a.intValue() + 1); // unbox 1000, add 1, box 1001
将此与使用 int
的其他版本相比较,当使用 Integer
时,显然有三个额外的方法调用。在 valueOf
的情况下,每个调用都将创建并初始化一个新的 Integer
对象。所有这些额外的装箱和拆箱工作可能使第二个版本比第一个版本慢一个数量级。
除此之外,第二个版本是在每个 valueOf
调用中在堆上分配对象。虽然空间利用率是特定于平台的,但对于每个 Integer
对象,它可能在 16 字节的区域内。相比之下,int
版本需要零额外的堆空间,假设 a
和 b
是局部变量。
原语比其盒装等价物更快的另一个重要原因是它们各自的数组类型如何在内存中布局。
如果以 int[]
和 Integer[]
为例,在 int[]
的情况下,int
值在内存中连续排列。但是在 Integer[]
的情况下,它不是布局的值,而是对 Integer
对象的引用(指针),而这些对象又包含实际的 int
值。
除了是一个额外的间接级别,当迭代值时,这对于缓存局部性来说可能是一个很大的障碍。在 int[]
的情况下,CPU 可以立即将数组中的所有值提取到它的缓存中,因为它们在内存中是连续的。但是对于 Integer[]
,CPU 可能必须为每个元素执行额外的内存提取,因为该数组仅包含对实际值的引用。
简而言之,在 CPU 和内存资源中使用原始包装类型相对昂贵。不必要地使用它们是有效的。