前言:本博文将涉及的Java的自动装箱和自动拆箱,可以参考 这篇文章 和 官方教程 ,这里不再赘述。

首先,先看一个小程序:

public class Main {

    public static void main(String[] args){
Integer i1 = new Integer();
Integer i2 = new Integer();
System.out.println(i1 == i2); Integer i3 = ;
Integer i4 = ;
System.out.println(i3 == i4); Integer i5 = ;
Integer i6 = ;
System.out.println(i5 == i6);
}
}

上面的程序会依次输出false 、true、false。

第一个输出语句应该比较好理解,就是创建了不同的对象。但是第二跟第三个输出语句估计很多人就很难理解了。

要解释这个问题,需要从缓存说起。

缓存

  缓存是软件设计模式中一个非常有用的模式,缓存的实现方式有很多,不同方式可能存在性能上的差别。下面给出一个用数组实现的实例:

  (1)缓存类Cache_test

/*
* <p>
* 该对象使用数组实现了缓存,也就是,
* 每一次使用valueOf()创建新对象时,系统将会确认缓存中是否已经存在相应的对象(即data相等)。
* 假如存在,则直接返回缓存已存在的对象;
* 假如不存在,则创建一个新对象,存储到缓存中,并返回新创建的对象。
* </p>
*
* @author Harvin.
* @version 1.0
*/
public class Cache_test {
//需要存储的数据
private final String data; public Cache_test(String data){
this.data = data;
}
public String get_data(){
return this.data;
}
@Override
//直接判断是否是指向同一个对象
public boolean equals(Object obj){
if (this == obj) {
return true;
}
return false;
} //定义缓存的大小
private final static int MAX_SIZE = 10;
//使用数组来存储缓存
private static Cache_test[] cache
= new Cache_test[MAX_SIZE];
//定义当前缓存存储的位置
private static int pos = 0; /* 判断是否已经缓存了包含该data对象的Cache_test对象,
* 如果存在,则直接返回;
* 如果不存在,则直接创建后再将其返回
*/
public static Cache_test valueOf(String data){
for (int i = 0; i < MAX_SIZE; i++) {
if (cache[i] != null
&& cache[i].get_data().equals(data)) {
return cache[i];
}
}
if(MAX_SIZE == pos){
cache[0] = new Cache_test(data);
pos = 1;
}else{
cache[pos] = new Cache_test(data);
}
return cache[pos++];
}
}

Cache_test

  (2)测试类Main

public class Main {

    public static void main(String[] args){
Cache_test ct1 = new Cache_test("test1");
Cache_test ct2 = new Cache_test("test1");
//由于这里都是直接创建,所以下面会输出false;
System.out.println(ct1 == ct2); Cache_test ct3 = Cache_test.valueOf("test2");
Cache_test ct4 = Cache_test.valueOf("test2");
//由于这里使用的是valueOf()函数,将会使用到缓存。所以下面输出true.
System.out.println(ct3 == ct4);
}
}

Main

  上面的例子中,实现原理为:使用一个数组来缓存该类的对象,数组的长度为MAX_SIZE。每一次调用valueOf来创建对象时,缓存池将会先去查找缓存池中是否已经存在该对象,如果存在,则直接返回该对象,所以当输入两个相同data时,返回回来的对象是同一个,所以上面 ct3 和 ct4 为同一个对象。当缓存数组不存在该对象时,缓存池将根据传入的参数创建一个新的对象,再将其存储到缓存数组中。另外,在这里缓存池使用的是“先进先出”的原则。

PS:上面实例中,用于Cache_test的构造函数为共有,所以,允许创建不存储到缓存池中的对象,假如要强制使用缓存池,则可以将构造函数声明为private。

  

  了解了缓存原理后,我们来看看实际JDK中使用了缓存的类。

包装类 Integer 的缓存

类似于我们上面提到的缓存原理,Integer类如果使用new构造函数来创建对象,则每次都将返回全新的对象;假如采用了valueOf方法来创建对象,则会缓存该创建的对象。让我们来看看源码:

private static class IntegerCache {//内部类,注意它的属性都是定义为static final
static final inthigh; //缓存上界
static final Integer cache[];//cache缓存是一个存放Integer类型的数组 static {//静态语句块
final int low = -128;//缓存下界,值不可变 // high value may beconfigured by property
int h = 127;// h值,可以通过设置jdk的AutoBoxCacheMax参数调整(参见(3))
if (integerCacheHighPropValue !=null) {
// Use Long.decode here to avoid invoking methods that
// require Integer's autoboxing cache to be initialized
// 通过解码integerCacheHighPropValue,而得到一个候选的上界值
int i = Long.decode(integerCacheHighPropValue).intValue();
// 取较大的作为上界,但又不能大于Integer的边界MAX_VALUE
i = Math.max(i, 127);//上界最小为127
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - -low);
}
high = h; //上界确定,此时high默认一般是127
// 创建缓存块,注意缓存数组大小
cache =new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k <cache.length; k++)
cache[k] =new Integer(j++);// -128到high值逐一分配到缓存数组
} private IntegerCache() {}//构造方法,不需要构造什么

Integer

简单来说,就是使用了一个内部类IntegerCache 来管理缓存cache[]。但使用valueOf()方法时,系统将会判断是否存在于缓存池中。然而,请注意,这里有所不同的是,Integer类在加载时,就已经预先将一部分对象(即从-128到127)创建好了,也就是说每一次调用valueOf方法时,假如传入的值在-127到128之间,则Integer类直接返回已经创建好的对象,假如传入的参数值在此区间之外,则Integer类会创建一个全新的对象。

再看小程序

现在,让我们重新回来一开始的小程序。

(1)程序中 i1 和 i2 利用其构造函数进行构造,所以,两者是两个不同的对象,因此返回false。

(2)通过使用javap 查看字节码,可知 i3 和 i4 、i5 和 i6 的自动装箱事实上是调用了valueOf方法。i3 和 i4 的值在-128到127之间,所以直接使用缓存池的对象,而 i5 和 i6 超出该区间,所以创建的是新对象。

由此便可以得知所以输出结果了。

后记

通过资料查找和源码的查看,可以知道,除了Integer类外,还有Byte、Short、Long、Character也使用了缓存,而Flot、Double没有使用缓存。

相关资料

Integer中用静态内部类所作的缓存

Java中的装箱与拆箱

《Java 自动装箱和拆箱》

再学Java 之 Integer 包装类缓存的更多相关文章

  1. 从零开始学 Java - Spring 集成 Memcached 缓存配置(二)

    Memcached 客户端选择 上一篇文章 从零开始学 Java - Spring 集成 Memcached 缓存配置(一)中我们讲到这篇要谈客户端的选择,在 Java 中一般常用的有三个: Memc ...

  2. 再学Java 之 interface的成员变量

    前言:最近在学多线程,写“哲学家就餐问题(Dining Philosophers)”的时候,需要定义一个全局的变量,即哲学家的人数.常用的做法是在其中一个类中定义一个static final的变量,然 ...

  3. 从零开始学 Java - Spring 集成 Memcached 缓存配置(一)

    硬盘和内存的作用是什么 硬盘的作用毫无疑问我们大家都清楚,不就是用来存储数据文件的么?如照片.视频.各种文档或等等,肯定也有你喜欢的某位岛国老师的动作片,这个时候无论我们电脑是否关机重启它们永远在那里 ...

  4. 从源码看java中Integer的缓存问题

    在开始详细的说明问题之前,我们先看一段代码 public static void compare1(){ Integer i1 = 127, i2 = 127, i3 = 128, i4 = 128; ...

  5. java中Integer包装类的具体解说(java二进制操作,全部进制转换)

    程序猿都非常懒,你懂的! 今天为大家分享的是Integer这个包装类.在现实开发中,我们往往须要操作Integer,或者各种进制的转换等等.我今天就为大家具体解说一下Integer的使用吧.看代码: ...

  6. 再学Java 之 HashMap的底层实现

    今天参加欢聚时代的面试,我说我自己依靠自己的理解重新实现过HashMap.描述我自己的实现思想后,面试官问“hashmap”底层如果用数组不是效率比较低吗,不是更应该用红黑树吗?我一下子就蒙了.用数组 ...

  7. 再学Java 之 形参个数可变函数

    自Java 5后,Java允许定义形参个数可变的方法,从而允许运行过程中,为方法指定不确定个数的形参. 其定义方法的格式如下: void function_name ( type ... variab ...

  8. 再学Java 之 foreach循环

    从Java 5 之后,Java提供了一种新的循环:foreach循环,这种循环遍历数组和集合更加简洁. foreach循环语法格式如下: for ( type variableName : array ...

  9. 再学Java 之 解决No enclosing instance of type * is accessible

    深夜,临睡前写了个小程序,出了点小问题 public class Test_drive { public static void main(String[] args){ A a = new A(); ...

随机推荐

  1. C++ 动态分配 和 内存分配和内存释放

    动态分配 动态分配可以说是指针的关键所在.不需要通过定义变量,就可以将指针指向分配的内存.也许这个概念看起来比较模糊,但是确实比较简单.下面的代码示范如何为一个整数分配内存: int *pNumber ...

  2. Windows10和CentOS7双系统安装的一些小技巧

    我个人是先安装好了win10系统,且win10是单独在一个120g的盘里:而centOS7则是安装在另一个500g的磁盘的其中的380g里: 这里要着重注意的是,500g里分成380g的盘不要在win ...

  3. file.write(str),file.writelines(sequence)

    file.write(str)的参数是一个字符串,就是你要写入文件的内容.file.writelines(sequence)的参数是序列,比如列表,它会迭代帮你写入文件.

  4. Zend Studio 安装破解和汉化

    1.下载文件. 2.默认安装Zend Studio. 3.替换安装目录下plugins下的com.zend.verifier_12.5.1.v20150514-2003.jar文件 4.打开Zend ...

  5. windows7,windows8 64位系统 IIS7.0配置.net网站时报错:未能加载文件或程序集“XXX”或它的某一个依赖项。试图加载格式不正确的程序。

    背景: 在64位的操作系统中, IIS7.0配置.net网站时报错:未能加载文件或程序集“XXX”或它的某一个依赖项.试图加载格式不正确的程序. 解决办法: 把iis 对应的应用程序池 --高级设置- ...

  6. WPF/Silverlight开发的15个最佳实践(转发)

    英文出处:http://www.kunal-chowdhury.com/2010/08/some-best-practices-for-silverlight.html 作者:Kunal Chowdh ...

  7. Dapper 嵌套对象查询

    我有这样一个一对一关系的表结构:User->UserInfo User: /// <summary> /// 用户 /// </summary> [Serializabl ...

  8. 截图-----Selenium快速入门(十二)

    在自动化测试过程中,截图是常见的操作,因为有时候单靠程序无法判断是否已得到期望的结果,所以需要截图判断.又或者截图是作为判断的存证.Selenium的截图操作也是非常简单,而且自带了一个文件操作类Fi ...

  9. 初探angluar_01 目录结构分析及初始化项目

    简单说明:angular是模块化的,因此所有功能功能都属于组件 一.目录结构 e2e 端到端的测试目录  用来做自动测试的 node_modules 安装地依赖存放目录,package.json里安装 ...

  10. WPF点滴(2) 创建单实例应用程序

    最近有同事问道在应用程序启动之后,再次双击应用程序,如何保证不再启动新的应用程序,而是弹出之前已经启动的进程,本质上这就是创建一个单实例的WPF应用程序.在VS的工程树中有一个App.xaml和App ...