热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

深入分析面试常见问题之Integer,String中的==,equals

      首先,我锤5月份要开发布会了,在这祝我锤产品大卖。好了,言归正传。最近从上家公司辞职了,在家休息了一小段时间,然后也跑了一些面试,发现多家公司的笔试题中都有Intege

       首先,我锤5月份要开发布会了,在这祝我锤产品大卖。好了,言归正传。最近从上家公司辞职了,在家休息了一小段时间,然后也跑了一些面试,发现多家公司的笔试题中都有Integer和String,然后各种情况下用 ==,或者equals方法去比较,问是否相等。自己发现有些情况下,答题的时候也是连猜带蒙的。刚好最近有时间,人一闲下来呀,还是总得干点什么。所以,就准备好好研究下Integer,String在各种情况下用==,equals去比较,

                第一,究竟是在比较什么?

                第二,每一步是怎么进行的?

                希望这篇文章能够很好的回答上述的两个问题。

        我们先来看下Integer类在各种情况下的 ==,equals在比较什么:

        代码如下图:

        深入分析面试常见问题之Integer,String中的==,equals

        大家可以试着写下自己的判断结果,如果你的结果和运行结果不一致,那你就需要好好看下下面的解析了。如果你的结果和运行结果一致,你也可以看下解析,看你的判断思路是和和下面一致。

                System.out.println(i == j);      //  true
                System.out.println(is == js);   //  false

                System.out.println(i == d);  //  false
                System.out.println(i.equals(d));   // true

                System.out.println(i == 2);    // true
                System.out.println(d == 2);   // true

                System.out.println(2L == g);   // true

                System.out.println(i.equals(g));   // false

                System.out.println(g.equals(2));  // false

            众所周知的原则是:

                              (1) == 比较的是两个对象的内存地址

                          (2) equals方法没有重写的话是和 == 一样的,比较的是两个对象的地址,如果类重写了equals方法,比较的就是两个对象的内容。(像 Integer,String 等类,都重写了equals方法)

            这两条原则大体上是没错的,但是有些情况下,我们用两条原则去判断,有时候有点捉摸不定,有时候出现判断失误了。比如说,Integer i = 2,虚拟机究竟做了什么? is == js 为什么返回false ?  要解释第一个问题,我们需要  用 javap -c -l  IntegerTest.class,反编译IntegerTest,拿到字节码,看下虚拟机是怎么执行这段代码的。IntegerTest字节码如下:


  public static void main(java.lang.String[]);
    Code:
       0: iconst_2                          // 将 int 型 2 推送至栈顶
                        // 调用静态方法
       1: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
       4: astore_1                          // 将栈顶引用型数值存入第二个本地变量
      
       5: iconst_2
       6: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
       9: astore_2
                            // 创建一个对象,并将其引用值压入栈顶        
      10: new           #3                  // class java/lang/Integer
      13: dup                        // 复制栈顶数值并将复制值压入栈顶
      14: iconst_2                // 将 int 型 2 推送至栈顶
                        // 调用超类构造方法,实例初始化方法,私有方法
      15: invokespecial #4                  // Method java/lang/Integer."":(I)V
      18: astore_3                // 将栈顶引用型数值存入第四个本地变量
      
      19: new           #3                  // class java/lang/Integer
      22: dup
      23: sipush        200            // 将一个 短整型(-32768~32767)常量值推送至栈顶
      26: invokespecial #4                  // Method java/lang/Integer."":(I)V
      29: astore        4            // 将栈顶引用型数值存入指定本地变量
      
      31: sipush        200
      34: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      37: astore        5            
      
      39: sipush        200
      42: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      45: astore        6
     
      47: new           #5                  // class java/lang/Long
      50: dup
                        // 将 long 或 double 型 常量值从常量池中推送至栈顶
      51: ldc2_w        #6                  // long 2l
      54: invokespecial #8                  // Method java/lang/Long."":(J)V
      57: astore        7
      
      59: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
      62: aload_1
      63: aload_2
      64: if_acmpne     71            // 比较栈顶两引用型数值,当结果不相等时跳转
      67: iconst_1
      68: goto          72
      71: iconst_0
      72: invokevirtual #10                 // Method java/io/PrintStream.println:(Z)V
      
      75: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
      78: aload         5
      80: aload         6
      82: if_acmpne     89
      85: iconst_1
      86: goto          90
      89: iconst_0
      90: invokevirtual #10                 // Method java/io/PrintStream.println:(Z)V
    
      93: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
      96: aload_1
      97: aload_3
      98: if_acmpne     105
     101: iconst_1
     102: goto          106
     105: iconst_0
     106: invokevirtual #10                 // Method java/io/PrintStream.println:(Z)V
     
     109: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
     112: aload_1
     113: aload_3
     114: invokevirtual #11                 // Method java/lang/Integer.equals:(Ljava/lang/Object;)Z
     117: invokevirtual #10                 // Method java/io/PrintStream.println:(Z)V
     
     120: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
     123: aload_1
     124: invokevirtual #12                 // Method java/lang/Integer.intValue:()I
     127: iconst_2
     128: if_icmpne     135
     131: iconst_1
     132: goto          136
     135: iconst_0
     136: invokevirtual #10                 // Method java/io/PrintStream.println:(Z)V
     
     139: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
     142: aload_3
     143: invokevirtual #12                 // Method java/lang/Integer.intValue:()I
     146: iconst_2
     147: if_icmpne     154
     150: iconst_1
     151: goto          155
     154: iconst_0
     155: invokevirtual #10                 // Method java/io/PrintStream.println:(Z)V
     
     158: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
     161: ldc2_w        #6                  // long 2l
     164: aload         7
     166: invokevirtual #13                 // Method java/lang/Long.longValue:()J
     169: lcmp
     170: ifne          177
     173: iconst_1
     174: goto          178
     177: iconst_0
     178: invokevirtual #10                 // Method java/io/PrintStream.println:(Z)V
     
     181: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
     184: aload_1
     185: aload         7
     187: invokevirtual #11                 // Method java/lang/Integer.equals:(Ljava/lang/Object;)Z
     190: invokevirtual #10                 // Method java/io/PrintStream.println:(Z)V
     
     193: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
     196: aload         7
     198: iconst_2
     199: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
     202: invokevirtual #14                 // Method java/lang/Long.equals:(Ljava/lang/Object;)Z
     205: invokevirtual #10                 // Method java/io/PrintStream.println:(Z)V
     
     
     208: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
     211: new           #15                 // class java/lang/StringBuilder
     214: dup
     215: invokespecial #16                 // Method java/lang/StringBuilder."":()V
     218: ldc           #17                 // String  d =
     220: invokevirtual #18                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
     223: aload_3
     224: invokestatic  #19                 // Method java/lang/System.identityHashCode:(Ljava/lang/Object;)I
     227: invokevirtual #20                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
     230: ldc           #21                 // String   value_2 =
     232: invokevirtual #18                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
     235: iconst_2
     236: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
     239: invokestatic  #19                 // Method java/lang/System.identityHashCode:(Ljava/lang/Object;)I
     242: invokevirtual #20                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
     245: invokevirtual #22                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
     248: invokevirtual #23                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
     251: return

    LineNumberTable:

   // 代码中的行数 :对应的在字节码中的行数

      line 7: 0
      line 8: 5
      line 9: 10
      line 11: 19
      line 12: 31
      line 13: 39
      line 15: 47
      line 17: 59
      line 18: 75
      line 20: 93
      line 21: 109
      line 23: 120
      line 24: 139
      line 26: 158
      line 27: 181
      line 28: 193
      line 30: 208
      line 31: 224
      line 33: 236
      line 30: 248
      line 35: 251
}

Integer i = 2  这句代码在 字节码里对应以下几个指令:

        0: iconst_2                          // 将 int 型 2 推送至栈顶
                                                   // 调用静态方法
       1: invokestatic  #2              // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
       4: astore_1                         // 将栈顶引用型数值存入第二个本地变量

       通过字节码可以很清晰的看出,Integer i = 2, 是通过将 2 通过 Integer.valuesOf方法生成一个Integer对象,再将地址赋给 i;

      那么我们来看看System.out.println( i == j )  为什么返回 true, 以及

                              System.out.println( is == js ) 为什么返回 false ;

       首先,我们看看Integer.valueOf是怎么构造一个Integer并返回的。

       Integer.valueOf 的 源码:

        深入分析面试常见问题之Integer,String中的==,equals

再打开IntegerCache的代码:

深入分析面试常见问题之Integer,String中的==,equals

这两段代码的意思是,如果 Integer.valueOf( int q ) 方法传入的值如果是在 -128~127之间的话,那么返回的Integer是被 Integer cache [] 数组 缓存起来的。cache数组存的是Integer的引用。所以,

system.out.println( i = j )  i 和 j 的内存地址是一样的。因Integer类重写了equals和hashCode 方法,但我们可以通过System.identityHashCode(Object)来打印出 i 和 j 对象的内存地址;

深入分析面试常见问题之Integer,String中的==,equals

i 和 j 的 内存地址如下:

深入分析面试常见问题之Integer,String中的==,equals

我们能看到, i 和 j 的内存地址是一致的。

深入分析面试常见问题之Integer,String中的==,equals

通过这张图,可以得知,System.out.println(  is == js  ) 是 false,因为是两个不同的对象。

System.out.println( i = d) 肯定是 false ,因为是不同的对象。

System.out.println( i.equals(d) ) true,因为Integer重写了equals方法,比较的是对象的intValue。

那么以下两个 == 比较,返回的也是true ? 他们和 2 都是同一个对象么?

        System.out.println(i == 2);    true

        System.out.println(d == 2);   true

其中 i 和 2 是同一个对象,而 d 和 2 并不是同一个对象,我们可以打印出内存地址信息:

深入分析面试常见问题之Integer,String中的==,equals

可以看到打印出来的内存地址, i 和 2 是相同的 , 而 2 和 d 是不同的。如果值超过了 -128~127这个范围的话,那么, i , 2 ,d 内存地址都不同:

深入分析面试常见问题之Integer,String中的==,equals


既然内存不同,,这为什么返回true,我们可以看下字节码的内容:

    System.out.println( i == 2 )  在代码中是 23行,对应字节码中的120行

    line 23: 120

    字节码内容如下:

深入分析面试常见问题之Integer,String中的==,equals

123行中 加载 i ,

124行,调用了i 的实例方法Integer,intValue(),

127行,将常量2推向栈顶,

128行,比较栈顶两个int型数值的大小。就变成了——> 2 == 2   当然是true了。

System.out.println( d == 2 ) 也是一样。

System.out.println( 2L== g ) 同上。

接下来,我们看下:

        System.out.println(i.equals(g));      //  false

       

看下 Integer类 重写的equals方法代码:

深入分析面试常见问题之Integer,String中的==,equals

System.out.println(g.equals(2));     //  false

可以看下这段的字节码:

深入分析面试常见问题之Integer,String中的==,equals

再看Long类重写的equals方法代码,变成了 g .equals(Integer.valueOf(2)),那结果就是false了。

深入分析面试常见问题之Integer,String中的==,equals


Integer类 各种情况下 == 比较总结:

                1) Integer类的一些 == 比较,要注意到自动装箱机制的存在,(-128~127)这个范围之间,如果是通过 == 赋值的话,是返回IntegerCache[ ] 数组中的引用。超过这个范围,则返回一个新的 Integer对象。

               2)new Integer  和 数值( 比如说 2 比较的时候 ),分析字节码得知,new Integer对象在比较的时候调用到了intValue()方法,其实比较的是数值是否相等。

                 各种情况下 equals 比较总结:

                  1) 每个对象重写 equals()方法,对象的equals方法是用来比较同类型的两个对象内容是否相等的。而Integer.equals( Long ),肯定是不相等的。





我们再来看下String类在各种情况下的 ==,equals在比较什么:

      代码如下图:

    深入分析面试常见问题之Integer,String中的==,equals

运行结果:

        System.out.println( flower1 == flower2 );     //  true
        System.out.println( "big flower" == flower1);   // true
        System.out.println( flower1 == flower3 );   // false


System.out.println( flower1 == flower3 );   // false  这个 返回结果是false就不用解析了,因为是两个不同的对象了。

        System.out.println( flower1 == flower2 );     //  true
        System.out.println( "big flower" == flower1);   // true

这两个为true的话,从代码表面看不出什么,我们输出它的字节码文件来分析下吧。

字节码文件如下:

  public static void main(java.lang.String[]);
    Code:
                        // 将int、float、或String型常量值从常量池中推送至栈顶
       0: ldc           #2                  // String big flower
       2: astore_1                    // 将栈顶引用型数值存入第二个本地变量
      
       3: ldc           #2                  // String big flower
       5: astore_2
       
                        // 创建一个对象,并将其引用值压入栈顶
       6: new           #3                  // class java/lang/String
       9: dup                    // 复制栈顶数值并将复制值压入栈顶
      10: ldc           #2                  // String big flower
        
                        // 调用超类构造方法,实例初始化方法,私有方法
      12: invokespecial #4                  // Method java/lang/String."":(Ljava/lang/String;)V
      15: astore_3                // 将栈顶引用型数值存入第四个本地变量
                        // 获取指定类的静态域,并将其值压入栈顶
      16: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
      19: aload_1                // 将第二个引用类型本地变量推送至栈顶
      20: aload_2                    // 将第三个引用类型本地变量推送至栈顶
      21: if_acmpne     28            // 比较栈顶两引用型数值,当结果不相等时跳转
      24: iconst_1                // 将 int 型 1 推送至栈顶
      25: goto          29
      28: iconst_0                // 将 int 型 0 推送至栈顶
                        // 调用实例方法
      29: invokevirtual #6                  // Method java/io/PrintStream.println:(Z)V
      
      32: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
      35: ldc           #2                  // String big flower
      37: aload_1
      38: if_acmpne     45
      41: iconst_1
      42: goto          46
      45: iconst_0
      46: invokevirtual #6                  // Method java/io/PrintStream.println:(Z)V
      
      49: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
      52: aload_1
      53: aload_3
      54: if_acmpne     61
      57: iconst_1
      58: goto          62
      61: iconst_0
      62: invokevirtual #6                  // Method java/io/PrintStream.println:(Z)V
    
      65: return
    
    LineNumberTable:
      line 7: 0
      line 8: 3
      line 9: 6
      line 11: 16
      line 12: 32
      line 13: 49
      line 15: 65
}


首先看下 System.out.println( flower1 == flower2  ) 对应的字节码:

深入分析面试常见问题之Integer,String中的==,equals


首先是 aload_1( 对应的值是flower1 ) 再执行 aload_2( 对应 flower2 ),然后对这两个引用型的值进行比较。那我们就只能看下 aload1 和 aload2 ,存进去的时候是存了什么。字节码如下:

深入分析面试常见问题之Integer,String中的==,equals

发现 astore1 和 astore2 存的都是从常量池中拿出 big flower 这个 String,这就是说 flower1和flower2其实内存地址是一样的。我们可以通过java.lang.System.identityHashCode(Object) 打印出flower1和flower2的内存地址:

深入分析面试常见问题之Integer,String中的==,equals


通过观察字节码,发现 System.out.println( "big flower" == flower1); 比较的也是从常量池中取的 big flower 这个字符串,所以是相等的:

深入分析面试常见问题之Integer,String中的==,equals

我们可以打印出内存地址:

深入分析面试常见问题之Integer,String中的==,equals

关于String类的设计,其实是应用了 享元设计模式。什么是享元设计模式呢?就是采用一个共享来避免大量拥有相同内容对象的开销。在String中的体现就是,使用 = 赋值的时候,如果常量池中已经有了此字符串,那就直接引用。而通过new String(" big flower")去创建的时候,因为创建了一个新的String对象,在堆中分配了内存,所以每次内存地址都不一样。但是通过new创建的话,字符串内容如果常量池有了的话,还是从常量池中加载哦。如果常量池中没有的话,就创建一个放入常量池。

        运用上面所了解到的字节码知识,可以分析下  代码为什么返回 false :

        String flower1 = "big flower1";
        String flower2 = "big flower1";
        String d = "big flower1"+"big flower1";
        String s = flower1 + flower2;
        System.out.println(s == d);    // false;

      

Integer类 各种情况下 == 比较总结:

            1)使用 == 比较两个字符串的时候,要注意到常量池的存在,如果是比较

                    String flower1 = "big flower";

                    String flower2 = "big flower";

                    或者:    “big flower”,

                   当这三个其实都是从常量池中取到的对于 big flower的引用,所以,都是同一个对象。

            2) 只要是 赋值的时候,有 new String(" big flower"); 那么,使用 == 去比较,一定是返回false,永远都是不同的对象。只能通过equals去比较两个对象的内容;

       

            3) 拼接类的String对象比较:

               String d = "big flower1"+"big flower1";  在字节码层面就相当与   String d =big flower1big flower1;  ( 字节码 6-8 行 )

                   String s = flower1 + flower2; 这个 在字节码中看到的却是调用到了 StringBuilder去拼接的;( 字节码 9-24 行 )

          字节码如下:

        深入分析面试常见问题之Integer,String中的==,equals

    Integer和String类中的 各种 ==,equals()的比较分析就到这了。如果你觉得对你有帮助,欢迎付费:

深入分析面试常见问题之Integer,String中的==,equals


【1】 Java虚拟机字节码指令表

【2】 Java虚拟机字节码指令表分类

【3】 Java常量池(1)

【4】 Java常量池(2)

【5】String类的享元模式


                



推荐阅读
  • 本文介绍了在 Java 编程中遇到的一个常见错误:对象无法转换为 long 类型,并提供了详细的解决方案。 ... [详细]
  • Bootstrap 插件使用指南
    本文详细介绍了如何在 Web 前端开发中使用 Bootstrap 插件,包括自动触发插件的方法、插件的引用方式以及具体的实例。 ... [详细]
  • 本文整理了一份基础的嵌入式Linux工程师笔试题,涵盖填空题、编程题和简答题,旨在帮助考生更好地准备考试。 ... [详细]
  • MySQL初级篇——字符串、日期时间、流程控制函数的相关应用
    文章目录:1.字符串函数2.日期时间函数2.1获取日期时间2.2日期与时间戳的转换2.3获取年月日、时分秒、星期数、天数等函数2.4时间和秒钟的转换2. ... [详细]
  • 本文探讨了如何在 Java 中将多参数方法通过 Lambda 表达式传递给一个接受 List 的 Function。具体分析了 `OrderUtil` 类中的 `runInBatches` 方法及其使用场景。 ... [详细]
  • Java设计模式详解:解释器模式的应用与实现
    本文详细介绍了Java设计模式中的解释器模式,包括其定义、应用场景、优缺点以及具体的实现示例。通过音乐解释器的例子,帮助读者更好地理解和应用这一模式。 ... [详细]
  • 本文介绍了Java编程语言的基础知识,包括其历史背景、主要特性以及如何安装和配置JDK。此外,还详细讲解了如何编写和运行第一个Java程序,并简要介绍了Eclipse集成开发环境的安装和使用。 ... [详细]
  • 本文详细介绍了 com.apollographql.apollo.api.internal.Optional 类中的 orNull() 方法,并提供了多个实际代码示例,帮助开发者更好地理解和使用该方法。 ... [详细]
  • 2020年9月15日,Oracle正式发布了最新的JDK 15版本。本次更新带来了许多新特性,包括隐藏类、EdDSA签名算法、模式匹配、记录类、封闭类和文本块等。 ... [详细]
  • 我有一个从C项目编译的.o文件,该文件引用了名为init_static_pool ... [详细]
  • Hadoop的文件操作位于包org.apache.hadoop.fs里面,能够进行新建、删除、修改等操作。比较重要的几个类:(1)Configurati ... [详细]
  • JUC(三):深入解析AQS
    本文详细介绍了Java并发工具包中的核心类AQS(AbstractQueuedSynchronizer),包括其基本概念、数据结构、源码分析及核心方法的实现。 ... [详细]
  • Spring – Bean Life Cycle
    Spring – Bean Life Cycle ... [详细]
  • 深入解析 Lifecycle 的实现原理
    本文将详细介绍 Android Jetpack 中 Lifecycle 组件的实现原理,帮助开发者更好地理解和使用 Lifecycle,避免常见的内存泄漏问题。 ... [详细]
  • 解决Bootstrap DataTable Ajax请求重复问题
    在最近的一个项目中,我们使用了JQuery DataTable进行数据展示,虽然使用起来非常方便,但在测试过程中发现了一个问题:当查询条件改变时,有时查询结果的数据不正确。通过FireBug调试发现,点击搜索按钮时,会发送两次Ajax请求,一次是原条件的请求,一次是新条件的请求。 ... [详细]
author-avatar
菜鸟
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有