• 【JAVA基础】一:聊聊笔试常见到的 “==、equal” 比较是否相等的内在差别


    开始本文之前,先让我们记住一个口诀(这个口诀只针对基础的类比如String、Integer等,如果是自定义的类,需要看equal的具体实现):
    equal比较其值,== 比较地址

    这两天在走查代码的时候发现一个童鞋,在判断两个Integer类型的值是否相等的时候,用了==来判断,运行结果没错,因为这两个值在-128~127之间。 只是这种写法不符合规范,有隐患,我不是挑刺,但是觉得有必要给出个所以然来为啥要按照规范使用equal来比较对象值是否相等。

    让我们先来看个笔试的例子:请问写出下面的代码输出结果。

    public class JavaTest {
    
        public static void main(String args[]){
    
            Integer a = 127;
            Integer b = 127;
            Integer c = new Integer(127);
            Integer d = new Integer(127);
            int e = 127;
            
            // 1
            System.out.println(a == b);
            // 2
            System.out.println(a.equals(b));
            // 3
            System.out.println(a == c);
            // 4
            System.out.println(a.equals(c));
            // 5
            System.out.println(a == e);
            // 6
            System.out.println(a.equals(e));
    
            a = 200;
            b = 200;
            
            // 7
            System.out.println(a == b);
            // 8
            System.out.println(a.equals(b));
        }
    }
    

    答案是:
    true
    true
    false
    true
    true
    true
    false
    true

    运用口诀:equal比较其值,==比较地址
    第12行输出:true;因为它们的引用地址是同一个,这个地址存的值是127。
    第14行输出:true;因为它们的值都是127,所以equal比较其值,结果是相等。
    第16行输出:false;因为它们两个对象的引用地址不一样。
    第18行输出:true;同上面的第2处。
    第20行输出:true;这里会把e做装箱操作(从基本类型提升为包装对象类型),之后它们的引用地址是同一个,同第1处,后面解析java装箱、拆箱的概念。
    第22行输出:true;此处一样会对e装箱提升为Integer对象,然后它们的值都是127,所以equal结果为true。
    第28行输出:false;因为它们的引用地址不一样,它们不一样~,跟第1处不一样…下面会讲解。
    第30行输出:true;因为它们的值都是200,同前面第2、4、6。

    • 前面讲到java自动装箱、拆箱 ,简单点说,装箱就是把基本类型升级为对象包装类型;拆箱就是把对象包装类型转换为对应的基本类型。

    会自动拆箱、装箱的数据类型为:

    对象类型 基本类型
    Integer int (4字节)
    Short short (2字节)
    Float float (4字节)
    Double double (8字节)
    Byte byte (1字节)
    Long long (8字节)
    Character char (2字节)
    Boolean boolean (未定)

    下面让我们来看下具体的装箱、拆箱过程:我们编写一个Test类文件保存在Test.java文件中,UTF-8编码存储,内容如下:

    public class Test{
    
        public static void main(String[] args) {
            
            // 装箱
            Integer numInteger = 120;
    
            // 拆箱
            int num_int = numInteger;
        }
    }
    

    我们编译一下:

    javac Test.java

    如果提示“GBK编码…”之类的错误,在指令加个编码选项:

    javac -encoding utf-8 Test.java

    然后会在当前目录生成一个Test.class文件,我们通过javap工具反汇编看下这个class的内容:

    javap指令这里就不展开了,感兴趣的可以看文档:Javap官方文档

    javap -c Test.class

    上面的指令会直接显示反编译的内容,或者你也可以通过下面的指令来把反汇编的内容输出到文件中,比如输出当前目录的Test.bin文件里面:

    javap -c Test.class>Test.bin

    反汇编的内容如下:

    Compiled from "Test.java"
    public class Test {
      public Test();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."<init>":()V
           4: return
    
      public static void main(java.lang.String[]);
        Code:
           0: bipush        120
           2: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
           5: astore_1
           6: aload_1
           7: invokevirtual #3                  // Method java/lang/Integer.intValue:()I
          10: istore_2
          11: return
    }
    
    

    执行第6行代码Integer numInteger = 120;时,系统底层是执行了Integer.valueOf 来把数值120转换为一个新的Integer包装对象类型。

    执行第9行代码int num_int = numInteger;的时候,系统底层实际是执行了Integer.intValue 来把包装对象numInteger的intValue值取出来赋值给int类型变量num_int:

    • 那前面的JavaTest类中第12行跟28行的结果为啥不一样?
    Integer a = 127;
    Integer b = 127;
    
    // 1、结果为true
    System.out.println(a == b);
    
    a = 200;
    b = 200;
    
    // 7、结果为false
    System.out.println(a == b);
    
    

    感觉有点坑吧?我觉得出这个题目的考官是不是有点刁钻呀… 这个并没有啥神秘的,我们直接看源码吧。

    当代码中第1处的括号运算:a == b的时候 ,根据口诀,是判断这两个包装对象的地址是否一样,根据上面的反汇编代码可以看出来,代码中的Integer a = 127;对应的操作是Integer a = Integer.valueOf (127); Integer类的源码的valueOf如下:

    public final class Integer extends Number implements Comparable<Integer> {
        // ......
        
        /**
         * Cache to support the object identity semantics of autoboxing for values between
         * -128 and 127 (inclusive) as required by JLS.
         *
         * The cache is initialized on first usage.  The size of the cache
         * may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.
         * During VM initialization, java.lang.Integer.IntegerCache.high property
         * may be set and saved in the private system properties in the
         * sun.misc.VM class.
         */
    
        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 IntegerCache.high >= 127;
            }
    
            private IntegerCache() {}
        }
    
        /**
         * Returns an {@code Integer} instance representing the specified
         * {@code int} value.  If a new {@code Integer} instance is not
         * required, this method should generally be used in preference to
         * the constructor {@link #Integer(int)}, as this method is likely
         * to yield significantly better space and time performance by
         * caching frequently requested values.
         *
         * This method will always cache values in the range -128 to 127,
         * inclusive, and may cache other values outside of this range.
         *
         * @param  i an {@code int} value.
         * @return an {@code Integer} instance representing {@code i}.
         * @since  1.5
         */
        public static Integer valueOf(int i) {
            if (i >= IntegerCache.low && i <= IntegerCache.high)
                return IntegerCache.cache[i + (-IntegerCache.low)];
            return new Integer(i);
        }
        
        // ......
    }
    

    从上面的源码可以看出来,对于-128 ~ 127之间(闭包)的int值,通过valueOf得到其对应的Integer对象,是已经预先静态生成好的存放在数组中的对象,所以只要这个int值是在这区间范围的,得到的对象都是同一个。而如果传过来的int值不在 -128 ~ 127 区间范围,直接在最后new 一个新的Integer对象返回的,这就解释了为啥两个自动装箱数值为200的对象,用==来判断其地址,是不一样的,因为两个对象都不一样了。

    这里JDK应该是对于这些1字节长度的数值,总共28 = 256个,从-128~127,预先存起来,加快包装的速度、节省存储空间。那为啥不把两个字节、三个字节的都预先存起来呢?who knows?… 那为啥不把float、double的值也存起来?这两种类型的区间数是无限的好吗,看官你别逗了…

    综上所述:

    • == 对于基本类型的数值,是比较它们的值是否相等;对于包装对象这种引用型的变量,比较的是它们在堆中存储的地址是否相同。
    • equals 表示这两个变量的value数值是否相同。

    如果是自定义的类调用equal,这个需要具体看此类的equal有没有重写,具体如何实现的,比如:

    public class Test extends Object{
    
        int num = 0;
        public Test(int i){
            this.num = i;
        }
        public static void main(String[] args) {
            Test t1 = new Test(1);
            Test t2 = new Test(1);
            System.err.println(t1.equals(t2));
            System.err.println(t1 == t2);
        }
    }
    

    上面的程序执行输出为:
    false
    false

    第二个是false,这个很容易理解,但是第一个为啥呢,不是说好了equal是比较值么?但是这个Test自定义类并没有重写equal方法,所以调用的是Object里面的equal,我们看下它的源码:
    在这里插入图片描述

    也就是,Object的equal实现默认还是通过 == 来实现的… 明白了吧,所以我们自己来实现equal如下:

    public class Test extends Object{
    
        int num = 0;
        public Test(int i){
            this.num = i;
        }
    
        @Override
        public boolean equals(Object obj) {
            if(!(obj instanceof Test) || null == obj){
                return false;
            }
    
            return (num == ((Test) obj).num);
        }
    
        public static void main(String[] args) {
    
            Test t1 = new Test(1);
            Test t2 = new Test(1);
            System.err.println(t1.equals(t2));
            System.err.println(t1 == t2);
        }
    }
    

    这样,就能实现equal比较的是值了,跟前面的口诀保持一致了。

  • 相关阅读:
    [办公自动化]金山WPS和金山文档的培训资料
    博客的地址xxx.com/xxx的实现问题
    windows xp,windows 7电源管理总结
    说说项目的通用做法以及对于平台的使用
    c#通过OleDb连接sybase 15.5
    mmseg4j 中文分词 for .net版本
    使用CodeSmith批量生成文件
    swfupload上传时出现error #2049的解决方法
    linux常用命令
    GIT删除分支
  • 原文地址:https://www.cnblogs.com/xiaocy66/p/10589257.html
Copyright © 2020-2023  润新知