我们先来看下面这样一段代码。你可以先思考下,这段代码会输出什么样的结果。


Integer i1 = 56;
Integer i2 = 56;
Integer i3 = 129;
Integer i4 = 129;
System.out.println(i1 == i2);
System.out.println(i3 == i4);

如果不熟悉 Java 语言,你可能会觉得,i1 和 i2 值都是 56,i3 和 i4 值都是 129,i1 跟 i2 值相等,i3 跟 i4 值相等,所以输出结果应该是两个 true。这样的分析是不对的,主要还是因为你对 Java 语法不熟悉。要正确地分析上面的代码,我们需要弄清楚下面两个问题:

如何判定两个 Java 对象是否相等(也就代码中的“==”操作符的含义)?
什么是自动装箱(Autoboxing)和自动拆箱(Unboxing)?


所谓的自动装箱,就是自动将基本数据类型转换为包装器类型。所谓的自动拆箱,也就是自动将包装器类型转化为基本数据类型。具体的代码示例如下所示:


Integer i = 56; //自动装箱
int j = i; //自动拆箱

数值 56 是基本数据类型 int,当赋值给包装器类型(Integer)变量的时候,触发自动装箱操作,创建一个 Integer 类型的对象,并且赋值给变量 i。其底层相当于执行了下面这条语句:


Integer i = 59;底层执行了:Integer i = Integer.valueOf(59);

反过来,当把包装器类型的变量 i,赋值给基本数据类型变量 j 的时候,触发自动拆箱操作,将 i 中的数据取出,赋值给 j。其底层相当于执行了下面这条语句:


int j = i; 底层执行了:int j = i.intValue();

弄清楚了自动装箱和自动拆箱,我们再来看,如何判定两个对象是否相等?不过,在此之前,我们先要搞清楚,Java 对象在内存中是如何存储的。我们通过下面这个例子来说明一下。


User a = new User(123, 23); // id=123, age=23

针对这条语句,我画了一张内存存储结构图,如下所示。a 存储的值是 User 对象的内存地址,在图中就表现为 a 指向 User 对象。

当我们通过“==”来判定两个对象是否相等的时候,实际上是在判断两个局部变量存储的地址是否相同,换句话说,是在判断两个局部变量是否指向相同的对象。

了解了 Java 的这几个语法之后,我们重新看一下开头的那段代码


Integer i1 = 56;
Integer i2 = 56;
Integer i3 = 129;
Integer i4 = 129;
System.out.println(i1 == i2);
System.out.println(i3 == i4);

前 4 行赋值语句都会触发自动装箱操作,也就是会创建 Integer 对象并且赋值给 i1、i2、i3、i4 这四个变量。根据刚刚的讲解,i1、i2 尽管存储的数值相同,都是 56,但是指向不同的 Integer 对象,所以通过“”来判定是否相同的时候,会返回 false。同理,i3i4 判定语句也会返回 false。

不过,上面的分析还是不对,答案并非是两个 false,而是一个 true,一个 false。看到这里,你可能会比较纳闷了。实际上,这正是因为 Integer 用到了享元模式来复用对象,才导致了这样的运行结果。当我们通过自动装箱,也就是调用 valueOf() 来创建 Integer 对象的时候,如果要创建的 Integer 对象的值在 -128 到 127 之间,会从 IntegerCache 类中直接返回,否则才调用 new 方法创建。看代码更加清晰一些,Integer 类的 valueOf() 函数的具体代码如下所示:


public static Integer valueOf(int i) {if (i >= IntegerCache.low && i <= IntegerCache.high)return IntegerCache.cache[i + (-IntegerCache.low)];return new Integer(i);
}

实际上,这里的 IntegerCache 相当于,我们上一节课中讲的生成享元对象的工厂类,只不过名字不叫 xxxFactory 而已。我们来看它的具体代码实现。这个类是 Integer 的内部类,你也可以自行查看 JDK 源码。


/*** Cache to support the object identity semantics of autoboxing for values between* -128 and 127 (inclusive) as required by JLS.** The cache is initialized on first usage.  The size of the cache* may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.* During VM initialization, java.lang.Integer.IntegerCache.high property* may be set and saved in the private system properties in the* sun.misc.VM class.*/
private static class IntegerCache {static final int low = -128;static final int high;static final Integer cache[];static {// high value may be configured by propertyint h = 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_VALUEh = 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() {}
}

为什么 IntegerCache 只缓存 -128 到 127 之间的整型值呢?

IntegerCache 的代码实现中,当这个类被加载的时候,缓存的享元对象会被集中一次性创建好。毕竟整型值太多了,我们不可能在 IntegerCache 类中预先创建好所有的整型值,这样既占用太多内存,也使得加载 IntegerCache 类的时间过长。所以,我们只能选择缓存对于大部分应用来说最常用的整型值,也就是一个字节的大小(-128 到 127 之间的数据)

实际上,JDK 也提供了方法来让我们可以自定义缓存的最大值,有下面两种方式。如果你通过分析应用的 JVM 内存占用情况,发现 -128 到 255 之间的数据占用的内存比较多,你就可以用如下方式,将缓存的最大值从 127 调整到 255。不过,这里注意一下,JDK 并没有提供设置最小值的方法。


//方法一:
-Djava.lang.Integer.IntegerCache.high=255
//方法二:
-XX:AutoBoxCacheMax=255

现在,让我们再回到最开始的问题,因为 56 处于 -128 和 127 之间,i1 和 i2 会指向相同的享元对象,所以 i1==i2 返回 true。而 129 大于 127,并不会被缓存,每次都会创建一个全新的对象,也就是说,i3 和 i4 指向不同的 Integer 对象,所以 i3==i4 返回 false。

实际上,除了 Integer 类型之外,其他包装器类型,比如 Long、Short、Byte 等,也都利用了享元模式来缓存 -128 到 127 之间的数据。比如,Long 类型对应的 LongCache 享元工厂类及 valueOf() 函数代码如下所示:


private static class LongCache {private LongCache(){}static final Long cache[] = new Long[-(-128) + 127 + 1];static {for(int i = 0; i < cache.length; i++)cache[i] = new Long(i - 128);}
}public static Long valueOf(long l) {final int offset = 128;if (l >= -128 && l <= 127) { // will cachereturn LongCache.cache[(int)l + offset];}return new Long(l);
}

在我们平时的开发中,对于下面这样三种创建整型对象的方式,我们优先使用后两种


Integer a = new Integer(123);
Integer a = 123;
Integer a = Integer.valueOf(123);

第一种创建方式并不会使用到 IntegerCache,而后面两种创建方法可以利用 IntegerCache 缓存,返回共享的对象,以达到节省内存的目的。

举一个极端一点的例子,假设程序需要创建 1 万个 -128 到 127 之间的 Integer 对象。使用第一种创建方式,我们需要分配 1万个 Integer 对象的内存空间;使用后两种创建方式,我们最多只需要分配 256 个 Integer 对象的内存空间。

总结

  • Java Integer类用到了享元模式
  • 利用IntegerCache 缓存,返回共享的对象,可以达到节省内存的目的

享元模式在 Java Integer 中的应用相关推荐

  1. 享元模式在 Java String 中的应用

    享元模式在 Java String 类中的应用.同样,我们还是先来看一段代码,你觉得这段代码输出的结果是什么呢? String s1 = "小争哥"; String s2 = &q ...

  2. 享元模式-Flyweight(Java实现)

    享元模式-Flyweight 享元模式的主要目的是实现对象的共享,即共享池,当系统中对象多的时候可以减少内存的开销,通常与工厂模式一起使用. 本文中的例子如下: 使用享元模式: 小明想看编程技术的书, ...

  3. 享元模式(Java)

    下面是关于我所写的所有设计模式代码(还是建议自己手打或者想一个别的例子练习一次) (https://github.com/lihang212010/DesignPatterns-/tree/maste ...

  4. 享元模式在文本编辑器中的应用

    如何利用享元模式来优化文本编辑器的内存占用? 你可以把这里提到的文本编辑器想象成 Office 的 Word.不过,为了简化需求背景,我们假设这个文本编辑器只实现了文字编辑功能,不包含图片.表格等复杂 ...

  5. 设计模式-04.02-结构型设计模式-门面模式组合模式享元模式

    文章目录 门面模式(外观模式)[不常用] 门面模式的原理与实现 Demo案例-影院管理 传统方案 门面模式代码 TheaterLight Stereo Screen Projector Popcorn ...

  6. 23种设计模式之享元模式

    享元模式的定义 定义: 使用共享对象可有效的支持大量的细粒度的对象 通俗的说, 就是将类的通用属性抽出来,建立对象池,以达到限制对象数量的效果 上面定义中要求细粒度对象, 那么不可避免的使得对象数量多 ...

  7. 享元模式(Flyweight模式)

    在面向对象程序设计过程中,有时会面临要创建大量相同或相似对象实例的问题.创建那么多的对象将会耗费很多的系统资源,它是系统性能提高的一个瓶颈. 例如,围棋和五子棋中的黑白棋子,图像中的坐标点或颜色,局域 ...

  8. Java设计模式(装饰者模式-组合模式-外观模式-享元模式)

    Java设计模式Ⅳ 1.装饰者模式 1.1 装饰者模式概述 1.2 代码理解 2.组合模式 2.1 组合模式概述 2.2 代码理解 3.外观模式 3.1 外观模式概述 3.2 代码理解 4.享元模式 ...

  9. 图解Java设计模式学习笔记——结构型模式(适配器模式、桥接模式、装饰者模式、组合模式、外观模式、享元模式、代理模式)

    一.适配器模式(类适配器.对象适配器.接口适配器) 1.现实生活中的例子 泰国插座用的是两孔的(欧标),可以买个多功能转换插头(适配器),这样就可以使用了国内的电器了. 2.基本介绍 适配器模式(Ad ...

最新文章

  1. b区计算机考研招不满的大学,b区考研招不满的大学,适合调剂的学校有哪些
  2. 平流式隔油池计算_玻璃钢隔油池
  3. 三十四、深入Java中的泛型(上篇)
  4. 如何从SAP Fiori Launchpad里找到ABAP Development Tool的下载地址
  5. ethtool编译与内核实现介绍
  6. UML建模【转http://www.cnblogs.com/gaojun/archive/2010/04/27/1721802.html】
  7. Table 'xxx' doesn't exist
  8. mybatis实战教程(mybatis in action)之四:实现关联数据的查询(转)
  9. 行军导航过程中导向箭头
  10. Mysql之各种各样的函数啦
  11. foobar2000 播放dsd 512
  12. 高通Snapdragon Sensor Core(SSC)笔记
  13. 51单片机驱动 矩阵键盘原理及简单实现
  14. 史上最全源码安装ROS-BUG解决集合2:在树莓派4B上安装Raspbian Bluster aarch64系统 + ROS-Melodic
  15. PDF文件旋转最好用的方法
  16. 华为机试【机器人走迷宫】
  17. 回望曾经的自己!对话心灵的治愈系漫画
  18. 命令与征服2完全版+烈火风暴资料片
  19. [精简]托福核心词汇29
  20. 付呗聚合支付快速教程 分账篇③——多商户模式下分账提现全流程详解

热门文章

  1. Windows与Linux下查看占用端口的进程
  2. Ubuntu20.04 设置开机自启shell脚本
  3. python-《Python发展前景》
  4. ipv6单播地址包括哪两种类型_探秘联接|技术小课堂之BRAS设备IPv6地址分配方式...
  5. 例解List<Map<String, Object>>存放的对象问题
  6. 的谐音非诚勿扰_《菲诚勿扰》全网通缉24位女嘉宾
  7. Oracle 外部表加载监听日志,使用外部表访问监听日志
  8. 孔板流量计计算公式_带你全面了解各种流量计!
  9. 安卓app开发工具_手机APP开发会涉及到哪些知识点呢?
  10. 实现拍照_成电最强拍照地点合集,以及帮你实现它们的拍照师傅