一篇文章带你彻底弄懂Java的==符号

这篇具有很好参考价值的文章主要介绍了一篇文章带你彻底弄懂Java的==符号。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

本篇文章6735字,大概阅读时间20分钟。本文中使用到的JDK版本为1.8.0_301

目录

==符号的定义

基本类型中==符号的判断

String类型中==符号的判断


==符号的定义

        在Java中==符号的作用分为两类:

        1:==符号在八种基本类型的作用是比较对应基本类型的数值是否相等

        2:==符号在对象类型的作用是比较两个对象是否相等

        在对象类型中又有两类特殊情况,一种是基本类型中包装类对象,一种是String对象。前者由于存在缓存导致,后缀则是有字符串常量池的存在导致。

基本类型中==符号的判断

        在基本类型中==符号的作用是判断基本类型的数值是否相同

        int i1 = 1;
        int i2 = 2;
        int i3 = 1;
        System.out.println(i1 == i2); //false
        System.out.println(i1 == i3); //true

        char c1 = 'a';
        char c2 = 'b';
        char c3 = 'a';

        System.out.println(c1 == c2); //false
        System.out.println(c1 == c3); //true

        在基本类型的包装类中由于存在缓存以及自动拆箱/装箱,导致==符号的使用存在一定的差异,下面将会解释到。

        首先基本类型和包装类对应关系

基本类型 包装类型
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean

        在看一段代码(建议将代码复制粘贴到别处,下文会经常提到这段代码中的内容)

        Integer i1 = 127; //数值127 自动装箱
        Integer i2 = 127; //数值127 自动装箱
        System.out.println(i1 == i2);  //true

        Integer i3 = 128; //数值128 自动装箱
        Integer i4 = 128; //数值128 自动装箱
        System.out.println(i3 == i4);  //false

        Integer i5 = new Integer(127);
        Integer i6 = new Integer(127);
        System.out.println(i5 == i6);  //false

        Integer i7 = new Integer(128);
        Integer i8 = new Integer(128);
        System.out.println(i7 == i8);  //false

        int i9 = 127;
        System.out.println(i1 == i9); // true i1自动拆箱
        System.out.println(i5 == i9); // true i9自动拆箱

        首先i1==i2(true)和i3==i4(false) 这两种情况,要解释这两种情况,先看看自动拆箱/装箱背后的逻辑。将上面这段代码编译成Class文件,然后通过javap命令进行反编译,查看字节码的执行逻辑 。

        javap是jdk自带的反解析工具。它的作用就是根据class字节码文件,反解析出当前类对应的code区 (字节码指令)、局部变量表、异常表和代码行偏移量映射表、常量池等信息。

          通过执行 javap -c /xx.class 命令,输出下面内容java中==,java,java,jvm,开发语言

        在输出内容中,可以看到反汇编后,代码Integer i1 = 127在=号右边的127会调用Integer.valueOf(127)方法,将数值127包装成Integer(127)的Integer对象,同理i2、i3、i4也是相同原理。通过上述内容可以看出,自动装箱就是调用包装类的valueOf()方法将基本类型的值封装成对应的包装类对象。

        现在我们去看一下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的代码

    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; //127
            //创建一个Integer类型的cache数组长度为256
            cache = new Integer[(high - low) + 1];
            int j = low;
            //从下标0开始依次存入-128 -127 .... 126 127
            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() {}
    }

        在valueOf()方法中首先会判断传入的i在不在-128~127之间,如果在这区间就会调用 IntegerCache.cache,而cache是一个Integer对象的数组,并且是在static代码块中进行初始化,在IntegerCache对象初始化完成后,cache数组已经初始化完毕。所以如果在-128~127之间,直接返回cache中已经创建好的Integer对象,如果不在-128~127之间则会重新new一个Integer对象。

        通过上面的分析就可以得出 i1=127和i2=127在进行自动装箱的时候,通过valueOf方法返回的都是同一个已经在cache中存在的Integer(127)对象,而i3=128和i4=128则是直接通过new的方式创建的两个不同的Integer对象。而==符号在比较两个对象的时候是判断两个对象是否相等,明显i1和i2都是cache缓存中的同一个对象所以==比较结果为true,而i3和i4是两个不同的对象,所以==比较的结果是false。

        注意:通过上面的分析,在包装类比较数值是否相等的时候需要使用equals方法进行,不要使用==进行比较数值是否相等,由于有缓存的存在可能导致结果出现差异

        继续看上面的代码,在 i5、i6、i7、i8都是创建的不同的Integer对象,所以使用==符号的时候比较的结果都是false。

        在i9处定义了一个数值为127的常量,i1==i9和i5==i9比较的时候,我们继续查看反编译     java中==,java,java,jvm,开发语言         在i1==i9和i5==i9比较的时候 i1和i5会调用intValue()方法,获取Integer对象中存储的常量值

public int intValue() {return value;}

        所以i1==i9和i5==i9比较的是数值大小,在基本类型和包装类型进行比较的时候,包装类型通过调用xxxValue()方法获取对应的常量值,这个就是自动拆箱。 

        总结:

        1:在引用类型是包装类型的时候,而等号右边是基本类型的字面量,类似Integer i = 127的时候,会触发自动装箱,调用对应的valueOf()方法将字面量封装成包装类型。

        2:在基本类型的字面量和包装类对象比较的时候,包装类对象会调用对应xxxValue()方法,获取对象中的字面量值,所以比较的是字面量的值。

        3:基本类型的自动装箱就是调用对应包装类的valueOf()方法,而自动拆箱就是包装类调用xxxValue()方法。

        上面介绍的时候是使用int和int的包装类Integer作为例子,下面将会罗列出八种基本类型全部情况。 

基本类型 包装类型 缓存(valueOf方法)
byte Byte 存在缓存 -128~127之间
short Short 存在缓存 -128~127之间
int Integer 存在缓存 -128~127之间
long Long 存在缓存 -128~127之间
float Float 没有缓存
double Double 没有缓存
char Character 存在缓存 0~127之间
boolean Boolean 存在缓存 true/false

String类型中==符号的判断

        本文所用的JDK版本为1.8.0_301,在不同JDK版本中字符串常量池存在差异,所以接下来代码的运行环境都是1.8.0_301版本。

        在JDK8中字符串常量池的定义为:字符串常量池在堆中存储

        先看下面代码

String s1 = "abc";
String s2 = new String("abc");

        通过javap命令进行反编译

java中==,java,java,jvm,开发语言

        上图中ldc、astore这些JVM指令的解释可以查看这里,使用页面搜索来查找

        解读反编译后的内容

        Code 0:ldc指令将"abc"字面量存储到字符串常量池中,也就是String s1 = "abc"。如果常量池中不存在则需要在常量池中创建,如果常量池中有则直接使用。

        Code 2:将s1引用保存到局部变量表中

        Code 3:new一个字符串对象,对应的是String s2 = new String("abc")

        Code 6:将创建的对象s2压入操作数栈中

        Code 7:由于s2中的字符串"abc"已经存在在字符串常量池中,所以不需要再次创建"abc",直接从常量池中获取

        Code 9:初始化s2对象,调用String(str String)的构造方法,将已经存在常量池中的"abc"字符串传入构造方法,完成s2对象的创建

        Code 12:将s2引用保存到局部变量表中

java中==,java,java,jvm,开发语言

         再将上面一段代码,顺序颠倒

 String s2 = new String("abc");
 String s1 = "abc";

        再通过javap命令进行反编译

java中==,java,java,jvm,开发语言

        解读反编译后的内容

        Code 0:new一个字符串对象,对应的是String s2 = new String("abc")

        Code 3:将创建的对象s2压入操作数栈中

        Code 4:ldc指令将new String("abc")中的"abc"字面量存储到字符串常量池中,如果常量池中不存在则需要在常量池中创建,如果常量池中有则直接使用。这里"abc"由于在常量池中不存在,则第一次创建。

        Code 6:初始化s2对象,调用String(str String)的构造方法,将已经存在常量池中的"abc"字符串传入构造方法,完成s2对象的创建

        Code 9:将s2引用保存到局部变量表中

        Code 10:创建String s1 = "abc",由于"abc"已经在常量池中存在,则无需再次创建,直接使用常量池中的"abc"。

        Code 12:将创建的对象s1压入操作数栈中 

java中==,java,java,jvm,开发语言

         通过上面的分析,总结如下:

        1:jdk8中字符串常量池存是在堆中创建一块区域进行存储。

        2:字符串字面值的创建是直接在字符串常量池中进行创建,在创建前会检查常量池中是否已经存在对应的字符串,如果不存在则在常量池中进行创建,存在则直接使用。

        3:如果通过new 来创建字符串对象,首先会判断字面值是否在常量池中已经存在,如果不存在则在常量池中进行创建,存在则直接使用。然后将常量池中的字符串引用传递给在堆中创建的字符串对象,再将堆中创建的字符串对象传递给s2。

        通过上面的分析还可得出一个常见的面试问题String s2 = new String("abc"),s2的创建过程一共创建了几次对象?

        答案是一次或者两次

        一次:类似第一种情况,常量池中已经存在了对应的"abc"字符串,所以只需要在堆中创建new String("abc")对象即可,然后将堆中的对象指向s2。

        两次:类似第二种情况,常量池中不存在,则首先要在常量池中创建一次"abc"字符串对象,然后再在堆中第二次创建new String("abc")对象,并且把常量池中的对象指向堆中的对象,然后堆中的对象再指向s2。

        通过上面的分析,接下来可以研究==符号在字符串中的判断

        String s1 = "abc";
        String s2 = "abc";
        String s3 = new String("abc");
        String s4 = new String("abc");
        System.out.println(s1 == s2); //true
        System.out.println(s1 == s3); //false
        System.out.println(s3 == s4); //false

        s1和s2都是在常量池中创建的,并且在s2创建的时候"abc"字符串已经存在,所以s2得引用指向的是s1,故s1==s2为true,而s3和s4都是在堆上创建的对象,虽然s3和s4中字符串指向的是常量池中的同一个"abc",但是s3和s4是堆上的不同对象所以 s1==s3和s3==s4为false

java中==,java,java,jvm,开发语言        下面在分析几种使用+拼接字符串的情况下==符号的判断

        在判断之前先熟悉两个概念

        常量折叠优化:是指Java在编译期做的一个优化,将一些表达式在编译期能计算好的,不用放到运行期间进行计算

        例如  1+1或者 "ab" +"c"这些可以在编译期直接计算出表达式的结果 2和"abc"。

        字面量:指直接写在代码中的数字、字符串或布尔值。例如数值123 123.11或者字符串"abc"直接在代码中定义的值。

        接下来看+号拼接字符串的情况下==符号的判断

        第一种情况:常量折叠优化

        String s1 = "abc";
        String s2 = "a" + "b" + "c";
        System.out.println(s1 == s2); //true

        在这种情况下 s2在编译期间会进行常量折叠优化,直接拼接成"abc"。

java中==,java,java,jvm,开发语言        第二种情况:拼接字符串中有一个是变量或是通过new关键字实例化的对象

        String s1 = "abc";
        String s2 = "ab";
        String s3 = s2 + "c";
        String s4 = new String("ab") + "c";
        String s5 = new String("ab") + new String("c");
        System.out.println(s1 == s3); //false
        System.out.println(s1 == s4); //false
        System.out.println(s1 == s5); //false

        这种情况下,由于无法再编译器确定对象的值,只能在运行期间确定,s3、s4、s5都是在堆上创建的不同的字符串对象,所以比较结果都是false。    java中==,java,java,jvm,开发语言

         第三种情况:final修饰的字面量实例化的字符串编译器在编译前也可以直接确认它的值。

        String s1 = "abc";
        final String s2 = "ab";
        String s3 = s2 + "c";
        String s4 = s2 + new String("c");
        System.out.println(s1 == s3);  //true
        System.out.println(s1 == s4);  //false

        s2是被final修饰的常量,其值不会发生改变。在s3=s2+"c",可以看成 s3="ab"+"c"。所以在上面的判断中s1==s3为true。 而s4中虽然s2是常量带时候+后面跟着一个变量,所以在堆中创建了s4,故s1==s4为false。java中==,java,java,jvm,开发语言

        通过上面的分析,我们可以得出如下结论:

        1:字符串的字面量都是存在在常量池中,并且如果字面量在常量池中已经存在,则不会再次创建。

        2: 由于有常量折叠优化的存在,字符串的字面量在使用+号拼接的时候可以在编译期确定其值

        3:+号拼接字符串中有一个是变量或是通过new关键字实例化的对象,都是会在堆中创建其对象

        4:final修饰的字面量实例化的字符串编译器在编译前也可以直接确认它的值

        还有在字符串需要比较值是否相等的时候,需要使用equals()方法。并且equals方法的左边最好是确定不为空或者字符串的字面值。防止出现NullPointException异常。

        结束语:

        本文从八大基本类型和其包装类,以及String对象分析==字符在Java中的应用判断情况,相信通过本篇文章的阅读,可以使你如同文章标题一样,全面搞定==符号。当然这也是我写这篇文章的目的,感谢您能看到最后。

        方便自己,也方便他人,坚持原创!

        文章来源地址https://www.toymoban.com/news/detail-715575.html

到了这里,关于一篇文章带你彻底弄懂Java的==符号的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 假期算法提升(一篇文章带你彻底学会双指针)

    呀哈喽,我是结衣。 对于要参加程序设计比赛的人来说,算法永远都是一道绕不开的坎,你必须的去了解他才可以更好的去解决问题。非形式地说,算法就是任何良地计算过程,我们可以把算法看作是用于求良说明地计算问题地工具。那么今天我们学到的就是其中最基础的一

    2024年02月19日
    浏览(47)
  • 一篇文章带你走进Java(保姆级)

    手打不易,希望对各位还在徘徊学什么语言的有帮助!!java不会让你失望!! Java是一种优秀的程序设计语言,它具有令人赏心悦目的语法和易于理解的语义。 Java还是有一系列计算机软件和规范形成的技术体系,这个技术体系提供了完整的用于软件开发和跨平台部署的支持

    2024年02月15日
    浏览(55)
  • 【数据结构】一篇文章带你彻底学会《后缀表达式》

    创作不易,本篇文章如果帮助到了你,还请点赞 关注支持一下♡𖥦)!! 主页专栏有更多知识,如有疑问欢迎大家指正讨论,共同进步! 🔥c语言系列专栏:c语言之路重点知识整合 🔥 给大家跳段街舞感谢支持!ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ 后缀表

    2024年02月05日
    浏览(57)
  • 【Spring框架】一篇文章带你彻底搞懂Spring解决循环依赖的底层原理

    目录 一、前言 二、什么是循环依赖 三、Spring Bean 的循环依赖问题 3.1 Bean 的创建步骤 3.2 为什么 Spring Bean 会产生循环依赖问题? 3.3 什么情况下循环依赖可以被处理? 四、Spring 如何解决循环依赖问题? 4.0 什么是三级缓存 4.1 简单的循环依赖(没有AOP) 4.1.0 创建Bean的前期流

    2024年04月17日
    浏览(58)
  • ElasticSearch篇——认识、安装和使用IK分词器插件,一篇文章带你彻底拿下!

    一、什么是IK分词器 所谓分词,即把一段中文或者别的划分成一个个的,我们在搜索时会把自己的信息进行分词,会把数据库中或者索引库中的数据进行分词,然后进行一个匹配的操作,默认的中文分词器是将每一个字看成一个词,比如“我爱中国”会被分成“我”、

    2024年02月03日
    浏览(38)
  • 前端开发攻略---从源码角度分析Vue3的Propy比Vue2的defineproperty到底好在哪里。一篇文章让你彻底弄懂响应式原理。

    Vue的响应式到底要干什么? 无非就是要知道当你 读取 对象的时候,要知道它读了。要做一些别的事情 无非就是要知道当你 修改 对象的时候,要知道它改了。要做一些别的事情 所以要想一个办法, 把读取和修改的动作变成一个函数 ,读取和修改的时候分别调用对应的函数

    2024年04月17日
    浏览(46)
  • 【Java递归】一篇文章带你了解,什么是递归 ,递归的特点,递归应用场景,递归练习题

    博主: 東方幻想郷 专栏分类: Java | 从入门到入坟 🌟递归是一种在方法通过 调用自身 来解决某些问题的技术,它可以将一些问题,分为更小,更细类似的子问题,逐步解决, 直到问题被简化到某个基本情况 ,最后可以直接拿到答案。 递归是一种函数调用自身的方法 递归

    2024年02月06日
    浏览(52)
  • 一篇文章带你了解Java发送邮件:使用JavaMail API发送电子邮件的注意事项、发送附件等

    作者:Stevedash 发表于:2023年8月13日 15点48分 来源:Java 发送邮件 | 菜鸟教程 (runoob.com) 电子邮件在现代通信中扮演着至关重要的角色,而在Java编程中,我们可以利用JavaMail API来方便地实现发送电子邮件的功能。本篇博客将向您介绍如何使用JavaMail API来发送电子邮件,以及一些

    2024年02月13日
    浏览(66)
  • 数据结构与算法之美学习笔记:41 | 动态规划理论:一篇文章带你彻底搞懂最优子结构、无后效性和重复子问题

    本节课程思维导图: 今天,我主要讲动态规划的一些理论知识。学完这节内容,可以帮你解决这样几个问题:什么样的问题可以用动态规划解决?解决动态规划问题的一般思考过程是什么样的?贪心、分治、回溯、动态规划这四种算法思想又有什么区别和联系? 什么样的问

    2024年02月02日
    浏览(66)
  • deque用法深度解析,一篇文章弄懂deque容器各种操作

    🖱 博客主页:在下马农的碎碎念 ✍ 本文由在下马农原创,首发于CSDN 📆 首发时间:2022/01/11 📅 最近更新时间:2022/01/11 🤵 此马非凡马,房星本是星。向前敲瘦骨,犹自带铜声。 📇 系列文章目录: 暂无 🙏作者水平有限,如发现错误,请留言轰炸哦!万分感谢! 🤗码字不

    2023年04月24日
    浏览(51)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包