IT七剑客 IT七剑客
首页
wresource
郭霖
孤寒者
IT邦德
沉默王二
老麦
stackoverflow
GitHub (opens new window)
首页
wresource
郭霖
孤寒者
IT邦德
沉默王二
老麦
stackoverflow
GitHub (opens new window)
  • Java基础语法

  • 程序人生

  • 实用工具

  • Java重要知识点

    • Java中常用的48个关键字和2个保留字
    • 深入理解Java中的注解
    • 深入剖析Java中的拆箱和装箱
    • 先有Class还是先有Object?
    • 详解Java中Comparable和Comparator的区别
    • 彻底讲明白的Java浅拷贝与深拷贝
    • Java枚举(enum)
    • 一次性搞清楚equals和hashCode
    • 大白话说Java反射:入门、使用、原理
    • 深入理解Java泛型
    • 深入理解Java中的hashCode方法
    • 深入理解Java中的不可变对象
    • instanceof关键字是如何实现的?
    • Java中int、Integer、new Integer之间的区别
    • Java命名规范,告别编码 5 分钟,命名 2 小时
    • 彻底弄懂Java中的Unicode和UTF-8编码
    • jdk9为何要将String的底层实现由char[]改成了byte[]?
    • 为什么JDK源码中,无限循环大多使用for(;;)而不是while(true)?
    • Java 方法重写 Override 和方法重载 Overload 的区别,一下子就明白了
    • Java重写(Overriding)时应当遵守的11条规则
    • Java到底是值传递还是引用传递?
    • Java不能实现真正泛型的原因是什么?
    • Java中可变参数的使用
  • Java工具

  • 数组与字符串

  • 沉默王二 Java
  • Java重要知识点
沉默王二
2023-01-22

Java中int、Integer、new Integer之间的区别

# Java中int、Integer、new Integer之间的区别

“三妹,今天我们来补一个小的知识点:Java 数据类型缓存池。”我喝了一口枸杞泡的茶后对三妹说,“考你一个问题哈:new Integer(18) 与 Integer.valueOf(18) 的区别是什么?”

“难道不一样吗?”三妹有点诧异。

“不一样的。”我笑着说。

  • new Integer(18) 每次都会新建一个对象;
  • Integer.valueOf(18) 会使⽤用缓存池中的对象,多次调用只会取同⼀一个对象的引用。

来看下面这段代码:

Integer x = new Integer(18);
Integer y = new Integer(18);
System.out.println(x == y);

Integer z = Integer.valueOf(18);
Integer k = Integer.valueOf(18);
System.out.println(z == k);

Integer m = Integer.valueOf(300);
Integer p = Integer.valueOf(300);
System.out.println(m == p);
1
2
3
4
5
6
7
8
9
10
11

来看一下输出结果吧:

false
true
false
1
2
3

“第一个 false,我知道原因,因为 new 出来的是不同的对象,地址不同。”三妹解释道,“第二个和第三个我认为都应该是 true 啊,为什么第三个会输出 false 呢?这个我理解不了。”

“其实原因也很简单。”我胸有成竹地说。

基本数据类型的包装类除了 Float 和 Double 之外,其他六个包装器类(Byte、Short、Integer、Long、Character、Boolean)都有常量缓存池。

  • Byte:-128~127,也就是所有的 byte 值
  • Short:-128~127
  • Long:-128~127
  • Character:\u0000 - \u007F
  • Boolean:true 和 false

拿 Integer 来举例子,Integer 类内部中内置了 256 个 Integer 类型的缓存数据,当使用的数据范围在 -128~127 之间时,会直接返回常量池中数据的引用,而不是创建对象,超过这个范围时会创建新的对象。

18 在 -128~127 之间,300 不在。

来看一下 valueOf 方法的源码吧。

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

“哦,原来是因为 Integer.IntegerCache 这个内部类的原因啊!”三妹好像发现了新大陆。

“是滴。来看一下 IntegerCache 这个静态内部类的源码吧。”

private static class IntegerCache {
    static final int low = -128;
    static final int high;
    static final Integer cache[];

    static {
        // high value may be configured by property
        int 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_VALUE
                h = 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 Integer.IntegerCache.high >= 127;
    }

    private IntegerCache() {}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

之前我们在学习 static 关键字 (opens new window)的时候,提到过静态代码块,还记得吧?三妹。静态代码块通常用来初始化一些静态变量,它会优先于 main() 方法执行。

在静态代码块中,low 为 -128,也就是缓存池的最小值;high 默认为 127,也就是缓存池的最大值,共计 256 个。

可以在 JVM 启动的时候,通过 -XX:AutoBoxCacheMax=NNN 来设置缓存池的大小,当然了,不能无限大,最大到 Integer.MAX_VALUE -129

之后,初始化 cache 数组的大小,然后遍历填充,下标从 0 开始。

“明白了吧?三妹。”我喝了一口水后,扭头看了看旁边的三妹。

“这段代码不难理解,难理解的是 assert Integer.IntegerCache.high >= 127;,这行代码是干嘛的呀?”三妹很是不解。

“哦哦,你挺细心的呀!”三妹真不错,求知欲望越来越强烈了。

assert 是 Java 中的一个关键字,寓意是断言,为了方便调试程序,并不是发布程序的组成部分。

默认情况下,断言是关闭的,可以在命令行运行 Java 程序的时候加上 -ea 参数打开断言。

来看这段代码。

public class AssertTest {
    public static void main(String[] args) {
        int high = 126;
        assert high >= 127;
    }
}
1
2
3
4
5
6

假设手动设置的缓存池大小为 126,显然不太符合缓存池的预期值 127,结果会输出什么呢?

直接在 Intellij IDEA 中打开命令行终端,进入 classes 文件,执行:

 /usr/libexec/java_home -v 1.8 --exec java -ea com.itwanger.s51.AssertTest
1

我用的 macOS 环境,装了好多个版本的 JDK,该命令可以切换到 JDK 8

也可以不指定 Java 版本直接执行(加上 -ea 参数):

java -ea com.itwanger.s51.AssertTest
1

“呀,报错了呀。”三妹喊道。

Exception in thread "main" java.lang.AssertionError
        at com.itwanger.s51.AssertTest.main(AssertTest.java:9)
1
2

“是滴,因为 126 小于 127。”我回答道。

“原来 assert 是这样用的啊,我明白了。”三妹表示学会了。

“那,缓存池之所以存在的原因也是因为这样做可以提高程序的整体性能,因为相对来说,比如说 Integer,-128~127 这个范围内的 256 个数字使用的频率会高一点。”我总结道。

“get 了!二哥你真棒,又学到了。”三妹很开心~


最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关 等等等等……详情戳:可以说是2022年全网最全的学习和找工作的PDF资源了 (opens new window)

关注二哥的原创公众号 沉默王二,回复111 即可免费领取。

编辑 (opens new window)
#沉默王二#Java
上次更新: 2023/01/22, 15:00:47
instanceof关键字是如何实现的?
Java命名规范,告别编码 5 分钟,命名 2 小时

← instanceof关键字是如何实现的? Java命名规范,告别编码 5 分钟,命名 2 小时→

最近更新
01
Coding 102 Writing code other people can read
02-26
02
Kotlin Flow响应式编程,StateFlow和SharedFlow
02-05
03
Kotlin Flow响应式编程,操作符函数进阶
02-05
更多文章>
Theme by Vdoing | Copyright © 2022-2023 IT七剑客 | MIT License
  • 闽ICP备2021006579号-4
  • 闽公网安备 35012102500470号
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式