Java中的包装类的比较(Integer为例)_integer 包装类之间比较-程序员宅基地

技术标签: Java  

看下面一段程序(来自《深入理解Java虚拟机(第三版)》P376代码清单10-13 自动装箱的陷阱)

        Integer a = 1;
        Integer b = 2;
        Integer c = 3;
        Integer d = 3;
        Integer e = 321;
        Integer f = 321;
        Long g = 3L;

		System.out.println(c == d);
        System.out.println(e == f);
        System.out.println(c == (a+b));
        System.out.println(c.equals((a+b)));
        System.out.println(g == (a+b));
        System.out.println(g.equals(a+b));

结果为:

true
false
true
true
true
false

可能刚看到这个结果很迷惘,下面结合代码生成的字节码来分析一下(由于字节码很长,所以分段讲解):
第一部分, 变量的赋值

		/**
			当int取值-1~5采用iconst指令,
			取值-128~-2||6~127采用bipush指令,
			取值-32768~-129||128~32767采用sipush指令,
			取值-2147483648~-32768||32768~2147483647采用 ldc 指令。
		*/
		
		// 变量a
         0: iconst_1
         1: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
         4: astore_1
		 
		// 变量b
         5: iconst_2
         6: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
         9: astore_2
		
		// 变量c
        10: iconst_3
        11: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        14: astore_3
		
		// 变量d
        15: iconst_3
        16: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        19: astore        4
		
		// 变量e
        21: sipush        321
        24: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        27: astore        5
		
		// 变量f
        29: sipush        321
        32: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        35: astore        6
		
		// 变量g
        37: ldc2_w        #3                  // long 3l
        40: invokestatic  #5                  // Method java/lang/Long.valueOf:(J)Ljava/lang/Long;
        43: astore        7

a == b

这一段的字节码如下:

		// 由于对象缓存,所以c和d指向的是同一个对象,所以输出 true
        45: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
        48: aload_3
        49: aload         4
        51: if_acmpne     58
        54: iconst_1
        55: goto          59
        58: iconst_0
        59: invokevirtual #7                  // Method java/io/PrintStream.println:(Z)V
        62: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;

Integer的valueOf方法

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

其中IntegerCache是Integer的私有静态内部类

    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 =
                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 IntegerCache.high >= 127;
        }

        private IntegerCache() {
    }
    }

可以看出,内部有一个cache数组,保存着初始化时缓存的Integer对象,其下界low固定为-128, 上界high默认为127, 但是可以通过指定系统属性来调整,比如:

-Djava.lang.Integer.IntegerCache.high=321

调整cache high值

c和d的值为3,由于Byte, Short, Integer, Long四个包装类默认情况下会缓存值为 -128 ~ 127之间的对象,Character会缓存\u00’ ~ '\u7F’之间的数据。 所以输出为true,。
但是如果c和d通过如下方式创建:

Integer c = new Integer(3);
Integer d = new Integer(3);

c == dfalse。因为是创建的两个不同的对象,尽管值相等。但是从JDK9开始,上面的构造器就被声明为Deprecated的,原因如下:

It is rarely appropriate to use this constructor. The static factory
 {@link #valueOf(int)} is generally a better choice, as it is
ikely to yield significantly better space and time performance.

e == f

这一段的字节码如下:

		// 第二次输出 false
        65: aload         5
        67: aload         6
        69: if_acmpne     76
        72: iconst_1
        73: goto          77
        76: iconst_0
        77: invokevirtual #7                  // Method java/io/PrintStream.println:(Z)V
        80: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;

在赋值时,通过执行invokespecial指令,调用Integer类的静态方法valueOf,实现自动装箱,又由于默认情况下缓存上界为127,所以返回两个不同的对象。

c == (a+b)

这一部分的字节码如下:

		// 第三次输出 true
        83: aload_3
        84: invokevirtual #8                  // Method java/lang/Integer.intValue:()I
        87: aload_1
        88: invokevirtual #8                  // Method java/lang/Integer.intValue:()I
        91: aload_2
        92: invokevirtual #8                  // Method java/lang/Integer.intValue:()I
        95: iadd
        96: if_icmpne     103					//比较的是操作栈上的数值
        99: iconst_1
       100: goto          104
       103: iconst_0
       104: invokevirtual #7                  // Method java/io/PrintStream.println:(Z)V
       107: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;

可以看到,分别将变量 c, a, b压栈,同时实行拆箱操作。紧接着执行a和b的加法操作,然后比较栈顶和次栈顶,很明显,这里的值是数值,而不是对象 的地址。因为数值相等,所以输出true。

c.equals((a+b))

这一部分的字节码如下:

	   // 第四次输出 equal进行值比较 true
       110: aload_3
       111: aload_1
       112: invokevirtual #8                  // Method java/lang/Integer.intValue:()I
       115: aload_2
       116: invokevirtual #8                  // Method java/lang/Integer.intValue:()I
       119: iadd
       120: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
       123: invokevirtual #9                  // Method java/lang/Integer.equals:(Ljava/lang/Object;)Z
       126: invokevirtual #7                  // Method java/io/PrintStream.println:(Z)V
       129: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;

和第三次一样,先将c, a, b入栈,不同之处是c并没有拆箱,而且a和b完成加法操作后进行了装箱。因为equals的参数必须是Object及其子类(对象)。Integer重写了equals方法:

    public boolean equals(Object obj) {
    
        if (obj instanceof Integer) {
    
            return value == ((Integer)obj).intValue();
        }
        return false;
    }

g == (a+b)

这一段的字节码如下:

	   // 第五次输出 true
       132: aload         7
       134: invokevirtual #10                 // Method java/lang/Long.longValue:()J
       137: aload_1
       138: invokevirtual #8                  // Method java/lang/Integer.intValue:()I
       141: aload_2
       142: invokevirtual #8                  // Method java/lang/Integer.intValue:()I
       145: iadd
       146: i2l									//将a+b原本为int的结果转化为long
       147: lcmp
       148: ifne          155					//比较的是操作栈上的数值
       151: iconst_1
       152: goto          156
       155: iconst_0
       156: invokevirtual #7                  // Method java/io/PrintStream.println:(Z)V
       159: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;

首先分别将c, a, b入栈,同时拆箱。执行加法后将结果扩展为long类型,由于栈中保存的是拆箱后的数值,所以结果为true。

g.equals(a+b)

最后一段的字节码如下:

	   // 第六次输出 false
       162: aload         7
       164: aload_1
       165: invokevirtual #8                  // Method java/lang/Integer.intValue:()I
       168: aload_2
       169: invokevirtual #8                  // Method java/lang/Integer.intValue:()I
       172: iadd
       173: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
       176: invokevirtual #11                 // Method java/lang/Long.equals:(Ljava/lang/Object;)Z
       179: invokevirtual #7                  // Method java/io/PrintStream.println:(Z)V
       182: return

同样,把c, a, b入栈,c不拆箱,a和b拆箱,然后执行加法操作,最后将结果装箱。此时栈中保存的是对象的地址,注意,这里c是Long类型的,而a+b的结果是Integer类型的。所以在执行**Long.equals(Object obj)**时,类型不同当然结果为false。

    public boolean equals(Object obj) {
    
        if (obj instanceof Long) {
    
            return value == ((Long)obj).longValue();
        }
        return false;
    }

总结

  1. 当对包装类型赋值时,调用对应包装类型的valueOf方法进行封装,然后返回一个对象。
  2. 某些包装类可能含有缓存对象,而且缓存区间的上界是可以修改的,别只知道缓存范围为 -128~127
  3. 当包装类型对象在执行算术逻辑运算的情况下,会自动拆箱。
  4. 当包装类型对象在执行**==**比较的时候,包装类型并不会拆箱。当然原始类型比较时,直接是值比较,没有装箱操作。
  5. 这几个包装类的equals方法是两个对象之间的比较,而且必须是在相同对象的前提下,才进行值的比较。所以就算值相等,但是类型不同也返回false(如最后一个输出)。
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/LZCDTU/article/details/104242611

智能推荐

2020-11-30leetcode 贪心_0xc20a69ad22df7cdc34f792c7a7f7129490e6b07f-程序员宅基地

文章浏览阅读261次。leetcode 55 跳跃游戏思路一:这道题最容易想到的方法就是递归(dfs),但是很不幸的是,使用递归方法,即使使用了空间换时间,优化了花费的时间,依然会超时。。。思路二:贪心算法官方给出的题解也是贪心算法。。之前贪心算法训练得比较少,一时没有想到,需要仔细琢磨。。针对数组中的每个索引,在保证当前索引可达的前提下,寻找当前索引可达的最大范围。。..._0xc20a69ad22df7cdc34f792c7a7f7129490e6b07f

毕业设计 单片机自动追踪灭火系统 - arduino stm32 物联网 嵌入式_基于stm32灭火小车原理图-程序员宅基地

文章浏览阅读1k次。 这两年开始毕业设计和毕业答辩的要求和难度不断提升,传统的毕设题目缺少创新和亮点,往往达不到毕业答辩的要求,这两年不断有学弟学妹告诉学长自己做的项目系统达不到老师的要求。为了大家能够顺利以及最少的精力通过毕设,学长分享优质毕业设计项目,今天要分享的是基于单片机的自动追踪灭火系统学长这里给一个题目综合评分(每项满分5分)难度系数:4分工作量:4分创新点:3分基于arduino单片机的灭火装置,当传感器检测到火焰的时候,会自动控制舵机转向灭火。_基于stm32灭火小车原理图

antdesign Pro of vue 自定义菜单栏图标,导航菜单图标引入阿里巴巴矢量库IconFont自定义的图标_ant design pro react 侧边导航使用本地图标-程序员宅基地

文章浏览阅读1.5k次。antdesign Pro of vue自定义菜单突变,引入icontfont_ant design pro react 侧边导航使用本地图标

python基于小程序的物流管理系统flask-django-php-nodejs-程序员宅基地

文章浏览阅读674次,点赞8次,收藏19次。随着计算机技术和网络技术的不断发展,线上管理成为一种新兴的管理方式。既带动了物流等相关产业链的发展,又加剧了行业间的竞争。在这种情况之下,更为高质量的物流服务质量能够获得大批人员群体的青睐,为自身进一步开拓市场奠定良好的基础。

【聚类算法】篇二之KMeans聚类算法及其优化KMeans++,elkan K-Means,Mini Batch K-Means,K中心点算法_kmeans算法参数优化-程序员宅基地

文章浏览阅读3.9k次,点赞5次,收藏24次。篇二之KMeans聚类算法及其优化一、KMeans算法二、优化初始k个中心的KMeans++算法三、优化距离计算量的elkan K-Means算法四、Mini Batch K-Means参考KMeans算法是原型聚类的一种,原型聚类是指基于一组原型进行初始化,然后再利用迭代的方式对原型进行更新求解一、KMeans算法K均值算法基于最小化平方误差的原则,所有簇的平方误差和如下:μi是簇Ci..._kmeans算法参数优化

机器人学基础(2)-微分运动和速度-雅可比矩阵计算、雅可比矩阵求逆、计算关节运动速度_机器人雅可比矩阵选择题-程序员宅基地

文章浏览阅读8.9k次,点赞14次,收藏80次。通过学习本章内容,利用公式,已知机器人关节速度,利用雅可比矩阵可以得到机器人手的运动速度;已知机器人手的运动速度,求雅可比矩阵的逆可以得到机器人各关节的速度。同时也学习了不使用雅可比矩阵求关节速度的方法,利用机器人的逆微分运动方程,就可以确定每个关节速度为多少才能产生所期望的机器人手速度。知道了机器人逆运动方程和逆运动微分方程,即知道机器人在空间中的位置和速度。第一章运动学的正运动方程和逆运动方程是为了求得机器人的位置;本章节的微分运动是在已知位置的基础上求得机器人的运动速度和各个关节速度。_机器人雅可比矩阵选择题

随便推点

PintOS, kernel panic with -v option bochs on ubuntu_run didn't start up properly: no "pintos booting" -程序员宅基地

文章浏览阅读2.6k次。Run didn't start up properly: no "pintos booting" message pintos出错解决方案when i do "pintos -- run alarm-multiple" in .../build/ everything seems fine.but when i do "make ch_run didn't start up properly: no "pintos booting" message

实测:GPT-3.5、GPT-4、Newbing和文心一言,你该怎么选?_gpt 3.5 4 翻译能力-程序员宅基地

文章浏览阅读3.9k次,点赞3次,收藏3次。GPT-4的回答在理解程度、概括能力、语言表达和逻辑结构方面都表现较好,能够较为全面地反映原文的内容和作者的心境。4)文心一言的回答则更深入地分析了原文的意义,表达更具备思想性,用词也非常精准,同时也注重语言的音韵和节奏的变化,更符合文学审美。3)Newbing的回答语义理解相对较为深入,系统地解释了每个时间段的含义,并在简洁的词汇和句子中把它们纳入一个完整的故事中。2)GPT-3.5的回答与GPT-4的回答类似,但在表达方面更为流畅,用词更加优美,更符合文学的风格,有一定的深度。_gpt 3.5 4 翻译能力

工作流-Activiti 开发_java activiti工作流 任务不存在-程序员宅基地

文章浏览阅读1.2k次。工作流-Activiti 开发 工作流概述工作流(workflow)就是工作流程的计算模型,我们常见的请假电子流就是一个简单的工作流。JavaWeb 工作流开发准备使用Eclipse开发,需要安排工作流插件 离线安装安装步骤:Eclipse插件安装 new-&gt;other 检查安装是否成功 简单开发流程1.新建一个..._java activiti工作流 任务不存在

Android基础入门_activity_main.xml-程序员宅基地

文章浏览阅读1.6k次。Android Studio使用_activity_main.xml

JSF+EJB+JPA总体思路-程序员宅基地

文章浏览阅读551次。前言: JSF+EJB+JPA 其实我并没有想象中的难,只是想做好,建立在正确的地方应用,真正的困难. 良好的技术,在错误的地方做应用,这是唯一能够被垃圾. 用. 重量级企业应用能够使用这个主要的3层结构的实现,来添加很多其它的企业级应用.而这些基本上不怎么须要改动,能够说是能够独立开来维护的部分.至于耦合性,关键看怎样建立应用了,这个又和项目開始的分析和设计密切相关.目的: 建立..._jsf+ejb+jpa是什么

jsp 页面跳转传值-程序员宅基地

文章浏览阅读259次。2019独角兽企业重金招聘Python工程师标准>>> ..._jsp页面跳转传值

推荐文章

热门文章

相关标签