先说下保护性拷贝
引用赋值,一般是老应用将其值赋给新引用,结果就是新、老应用指向堆中的同一个对象。而保护性拷贝,是将老引用指向的对象拷贝一份新的出来,结果是老引用指向老对象,新引用指向新对象,但新、老对象的内容一样。这种通过创建副本对象来避免共享的手段称之为【保护性拷贝(defensive copy)】。
享元模式
因为如果每次都保护性拷贝会创建大量的对象,占用内存。【享元模式】指的是当要创建的对象 obj1
的内容,与内存中已存在的对象 obj2
的内容相同时,直接将 obj2
拿来使用。这样就能大大减少对象的创建,提高消耗,降低开销。
Long 中的享元模式
这是 Long.java
源码,里面提供了 valueOf
方法创建对象。如果参数 l
的取值范围在 -128~127
之间,它是直接从 LongCache 中拿对象,超出这个范围时,才会 new Long()
。
public static Long valueOf(long l) {
final int offset = 128;
if (l >= -128 && l <= 127) { // will cache
return LongCache.cache[(int)l + offset];
}
return new Long(l);
}
那 LongCache
做了什么呢?
private static class LongCache {
private LongCache(){}
// cache 数组存储 Long 对象。
static final Long cache[] = new Long[-(-128) + 127 + 1];
// 填充 ceche 数组。
static {
for(int i = 0; i < cache.length; i++)
cache[i] = new Long(i - 128);
}
}
当 jvm 第一次加载 LongCache 的 class 文件时做了三件事情:
- 创建数组 cache 并设定数组大小为 256。
- 执行静态代码块给静态数组 填充元素,将 -128~127 给填进去。
- 最后执行构造函数 LongCache()。
所以当我们调用 valueOf
的时时候,如果值在 -128~127,之间,其实是直接从 LongCache 中拿对象。
实际执行过程是怎么走的呢?
常写的代码
Long num1 = 1L;
Long num2 = 2L;
写法 1Long num1 = 1L
等价于 Long num1 = Long.valueOf(1L);
。为什么这么说呢?用了后者写法,IDEA 会提醒你简化。第二,Long.java
文件中总共有 4 个地方调用了 valueOf
方法,打个断点看一下。
执行 Long num1 = 1L:
- 进
valueOf(1)
; - 执行
LongCache.cache[1 + 128]
:- 加载 LongCache 类文件;
- 创建 cache 数组,并设置数组大小;
- 执行静态代码块,填充数组;
- 执行构造方法。
- 将
Long (1)
的引用赋值给 num1;
执行 Long num2 = 2L:
- 进
valueOf(1)
; - 执行
LongCache.cache[2 + 128]
: - 将
Long (2)
的引用赋值给 num2;
这里想想 Long mun3 = num1 + num2
跟第二句代码的执行过程是一样的。
都是在“拿”对象,而不是在 “创建”对象。