享元模式(下)
享元模式在 Java Integer 中的应用
自动装箱与自动拆箱
Java中的基本数据类型对应的有包装器类型,他们之前存在着自动装箱和拆箱的过程。
所谓的自动装箱,就是自动将基本数据类型转换为包装器类型。所谓的自动拆箱,也就是自 动将包装器类型转化为基本数据类型。具体的代码示例如下所示:
1 | Integer i = 56; //自动装箱 |
数值 56 是基本数据类型 int,当赋值给包装器类型(Integer)变量的时候,触发自动装箱 操作,创建一个 Integer 类型的对象,并且赋值给变量 i。其底层相当于执行了下面这条语句:
1 | Integer i = 59;底层执行了:Integer i = Integer.valueOf(59); |
反过来,当把包装器类型的变量 i,赋值给基本数据类型变量 j 的时候,触发自动拆箱操 作,将 i 中的数据取出,赋值给 j。其底层相当于执行了下面这条语句:
1 | int j = i; 底层执行了:int j = i.intValue(); |
对象存储
1 | User a = new User(123, 23); // id=123, age=23 |

当我们通过“==”来判定两个对象是否相等的时候,实际上是在判断两个局部变量存储的 地址是否相同,换句话说,是在判断两个局部变量是否指向相同的对象。
一个具体的例子
看下面的代码,它的结果是什么样的?
1 | Integer i1 = 56; |
前 4 行赋值语句都会触发自动装箱操作,也就是会创建 Integer 对象并且赋值给 i1、i2、 i3、i4 这四个变量。i1、i2 尽管存储的数值相同,都是 56,但是指向不同的 Integer 对象,所以通过“==”来判定是否相同的时候,会返回 false,同理第二个也是false。这样对吗?
答案并非是两个 false,而是一个 true,一个 false。
实际上,这正是因为 Integer 用到了享元模式来复用对象,才 导致了这样的运行结果。当我们通过自动装箱,也就是调用 valueOf() 来创建 Integer 对象的时候,如果要创建的 Integer 对象的值在 -128 到 127 之间,会从 IntegerCache 类中 直接返回,否则才调用 new 方法创建。
1 | public static Integer valueOf(int i) { |
因为 56 处于 -128 和 127 之间,i1 和 i2 会指向相同 的享元对象,所以 i1==i2 返回 true。而 129 大于 127,并不会被缓存,每次都会创建一 个全新的对象,也就是说,i3 和 i4 指向不同的 Integer 对象,所以 i3==i4 返回 false。
在平时开发中,优先使用后两种创建:
1 | Integer a = new Integer(123); |
第一种创建方式并不会使用到 IntegerCache,而后面两种创建方法可以利用 IntegerCache 缓存,返回共享的对象,以达到节省内存的目的。
享元模式在 Java String 中的应用
1 | String s1 = "aaa"; |
上面代码的运行结果是:一个 true,一个 false。String 类 利用享元模式来复用相同的字符串常量,JVM 会专门开辟 一块存储区来存储字符串常量,这块存储区叫作“字符串常量池”。
不过,String 类的享元模式的设计,跟 Integer 类稍微有些不同。Integer 类中要共享的对 象,是在类加载的时候,就集中一次性创建好的。但是,对于字符串来说,我们没法事先知 道要共享哪些字符串常量,所以没办法事先创建好,只能在某个字符串常量第一次被用到的 时候,存储到常量池中,当之后再用到的时候,直接引用常量池中已经存在的即可,就不需 要再重新创建了。
参考
《设计模式之美》