你不知道的 equals 和 ==

先来看一道 equals和 == 相关的面试题吧。

下面这段java代码的输出结果是?(不考虑java 1.5之前的老版本和换行)(-摘自牛客网)
publi class HelloWorld{
    public static void main(String[] args){
        Integer i1=127, i2=127, i3=128, i4=128;
        System.out.println(i1==i2);
        System.out.println(i1.equas(i2));
        System.out.println(i3==i4);
        System.out.println(i3.equals(i4));
    }
}

先告诉你答案是 true,true,false,true。

i1 == i2 和 i1.equals(i2) 这两个都是 true,大多数人应该可以答对。后面的 i3 == i4 和 i3.equals(i4) 估计就有不少人搞不清了。

Integer 是基本数据类型 int 的包装类,通过自动装箱和自动拆箱,实现 int 和 Integer 之间的转化,所以自动装箱和拆箱的本质要先搞清楚。

我们可以通过对 class 文件的反编译查看装箱和拆箱的过程。

写个测试类 Test,代码如下:

public class Test {
    public static void main(String[] args) {
        Integer i1 = 10;
        int i2 = i1;
    }
}

这里我使用 Windows 的 cmd 命令行,先将 java文件编译为字节码文件,再使用 jdk 自带的反编译工具javap,将字节码文件反编译,结果如下图所示。

5763525-a8be925ed9a87249.png
反编译

我们可以看到有 Test 类默认的构造方法,在main方法中又分别调用了 Integer 类中的静态方法 valuOf() 和 intValue(),所以实际上自动装箱背后使用的是 valueOf() 方法,自动拆箱背后使用的是 intValue() 方法。

要想彻底搞清问题,直接看 Integer 类的源代码比书上总结的理论更加有效,下面是我截取的一段 Integer 类的源码。

public final class Integer extends Number implements Comparable<Integer> {
    public static final int MIN_VALUE = -2147483648;
    public static final int MAX_VALUE = 2147483647;
    ...
    private final int value;
    public static final int SIZE = 32;
    public static final int BYTES = 4;
    ...
    // 自动装箱的 valueOf 方法
    public static Integer valueOf(int var0) {
        return var0 >= -128 && var0 <= Integer.IntegerCache.high ? Integer.IntegerCache.cache[var0 + 128] : new Integer(var0);
    }
    // 自动拆箱的 intValue() 方法
    public int intValue() {
        return this.value;
    }
    // equals 方法
    public boolean equals(Object var1) {
        if (var1 instanceof Integer) {
            return this.value == (Integer)var1;
        } else {
            return false;
        }
    }
    // Integer 类中的 IntegerCache 静态类
    private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer[] cache;

        private IntegerCache() {
        }

        static {
            int var0 = 127;
            String var1 = VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            int var2;
            if (var1 != null) {
                try {
                    var2 = Integer.parseInt(var1);
                    var2 = Math.max(var2, 127);
                    var0 = Math.min(var2, 2147483518);
                } catch (NumberFormatException var4) {
                    ;
                }
            }

            high = var0; # 给 high 赋值
            cache = new Integer[high - -128 + 1]; # cache 数组
            var2 = -128;

            for(int var3 = 0; var3 < cache.length; ++var3) {
                cache[var3] = new Integer(var2++);
            }

            assert high >= 127;

        }
    }
}

从源码中,我们可以看到Integer类是 final 关键字修饰的,也即是 Integer 类和 String 类一样都是不可被其他类继承的,并且还可以看到静态常量 MAX_VALUE,MIN_VALUE 的值就是 int 类型的范围,还有 SIZE,BYTES,这不就是 int 类型所占的位数和字节数嘛!类中还有其他变量的定义,读者可以自行查看阅读源码。

自动装箱的方法 valueOf() 中是返回一个三目运算后的值,代码大意就是,要装箱值 var0 如果在 -128 到 IntegerCache 类中类静态变量 high 值之间,就返回IntegerCache 类中 cache 数组中元素的值,否则使用 var0 的值构造一个 Integer 对象。现在目光转移到 IntegerCache 类,可以发现 high 的值实际为127,cache 数组的长度实际是 127 + 128 + 1 就是 256,并且下面对 cache 数组初始化赋值,从 -128 到 127。由于这些代码被包裹在 static 语句块中,所以在第一次使用 Integer 类时,这个 cache 数组就会建立起来。这其实是 Java 中的缓存策略,类似的还有 ByteCache、ShortCache、LongCache、CharacterCache 类,分别缓存 Byte 对象、Short 对象、Long 对象、Character 对象,读者可以自行查看这些类的源码,都是类似的。

自动拆箱的方法 intValue() 就比较简单了,直接返回当前 Integer 对象的 value 值。

在看 equals 方法之前,先思考下为什么要有 equals 方法?没错,equals 方法是比较两个对象是否相同的,确切的是,我们想使用 equals 方法来判断两个对象的值是否相等,学过 C++ 的读者应该知道 C++ 中可以重载运算符,但是你在 Java 中见过重载运算符吗?一些细心的读者会说,Java 中的 + 运算符不就是吗?我们可以用 + (加号)拼接两个 String 类型的字符串。看起来好像是,但对 Java 字符串有一定了解的读者知道,这其实只是个语法糖,看起来是其实并不一定是,你可以写个测试类,仿照上面反编译的过程,写两个字符串相加,看看背后调用的是什么方法。在这里简单的说下,+ (加法)背后是 StringBuilder 类的对象调用 append() 方法实现的。Java 之所以不想 C++ 那样可以重载运算符,一方面有历史原因,另一方面也是考虑到运算符重载会降低代码的可读性,不宜维护,这个仁者见仁,智者见智吧。个人也觉得直接对运算符重载不好,Python 中是通过重载运算符背后的方法来达到重载运算符的目的,如想重载 + (加号)运算符就重载 add() 方法,这样就清晰多了。

在 Integer 类的 equals() 方法中,首先判断要比较的对象是不是 Integer 类类型的,如果不是就直接返回 false,这里用到了 instanceof 关键字判断一个对象是不是某个类或者接口的实例。如果是,则用 ==(双等号)运算符判断两个对象中的 value 变量值是否相等,当然要先把要对比的对象类型转换为 Integer类型。Integer 中的 equals() 方法中也是用到了 ==(双等号)。equals() 方法没有那么神秘,就是类中一个普通的方法而已。这个方法最早是出现在 Java 中的 万象之母 Object 类中的,为的就是在比较两个对象是否相同而存在的,源码public boolean equals(Object obj) {return (this == obj);}很简单,就是用等号比较了下两个对象的引用地址是不是相等,子类要是想使用这个方法,就重载一下,不然直接和直接使用 ==(双等号)比较没啥区别。而在我们自定义的类中重写该方法时,还要重写 hash() 函数,一个对象的 hash 值可以看作是对象的身份证,其他的就不在这里多说了。

下面是玄学时间,什么才是两个对象相同?现实生活中,两个名字一样的人,能说他们一样吗?判定是不是同一个人是根据他的灵魂还是肉体,还是两者都是。如果认为判定这个人是根据他的灵魂,那么这个人的灵魂不死,是不是这个人还是这个人呢?。。。

上面这道题只是简单的数据类型,在 Java 中,==(双等号)可以比较基本数据类型的值是否相等,下面的代码结果为 false,true,现在大家应该都懂了。

Integer i1 = 127;
Byte b1 = 127;
int i2 = 127;
byte b2 = 127;
System.out.println(i1.equals(b1));
System.out.println(i2 == b2);

equals() 方法简单来说,就是你对两个对象相同怎么看的。对于两个字符串来说,如果这两个字符串的值相同,我们常规就认为他们是相等的,但是这两个字符串可能是两个对象,在哲学上,这就是你怎么看这个世界的问题了。

在这里,我也截取了 String 类中的 equals() 方法的源码。

public final class String implements Serializable, Comparable<String>, CharSequence {
    private final char[] value;
    public boolean equals(Object var1) {
            if (this == var1) {
                return true;
            } else {
                if (var1 instanceof String) {
                    String var2 = (String)var1;
                    int var3 = this.value.length;
                    if (var3 == var2.value.length) {
                        char[] var4 = this.value;
                        char[] var5 = var2.value;

                        for(int var6 = 0; var3-- != 0; ++var6) {
                            if (var4[var6] != var5[var6]) {
                                return false;
                            }
                        }

                        return true;
                    }
                }

                return false;
            }
        }
}

在 String 类的方法中,先判断这两个对象是不是同一个对象,如果是同一个对象,那么字符串值肯定一样嘛,直接返回 true。如果不是同一个对象,先判断要对比的对象是不是 String 类的实例,如果是,再看看两个对象中 value 数组是不是一样长,这个 value 数组中装的自然就是字符串中的每个字符啦。如果一样长,就再比较字符数组中每个字符是不是一样的,如果都是一样的,那么 equals 方法就返回 true。

另外 String 类中有一个很奇怪的方法,源码是 public native String intern();,这个 intern() 方法并没有实现体,就一个声明,那么它的实现在哪呢?修饰这个方法的有个 native 关键字,这个关键字说明这个方法是原生函数,也即是这个函数实现不是 Java 写的,所以想看源码只能去看其他语言的。编程领域,JNI(Java Native Interface,Java 本地接口)是一种编程框架,使得 Java 虚拟机中的 Java 程序可以调用本地应用/库,当然也可以被其他程序调用。Java 虚拟机底层需要调用本机操作系统的程序,这些程序很可能是 C、C++或者汇编语言编写的,Java 的跨平台性一方面也是要依赖本地操作系统的。

在 Java 中,String str1 = "abcd"; 和 String str2 = new String("abcd");创建对象是不一样的,前者是从 String 类的字符串的常量池拿对象(如果没有该对象就先添加再拿),后者是在堆内存中创建一个新的对象,这里也不多说。上面的 intern() 方法会查找常量池中国是否存在字符串值相等的字符串对象,如果存在就返回该字符串对象的引用,如果没有就添加该字符串进入常量池。

那么下面这道题答案就为 false, true, false。

String s1 = new String("abcd");
String s2 = "abcd";
String s3 = s2.intern();
System.out.println(s1 == s2);
System.out.println(s2 == s3);
System.out.println(s1 == s3);

在看 Java 源码时,可能觉得有的地方写的“不好”,因为老外写的嘛,老外也有他们的编程风格,命名习惯,我们要从中吸取好的营养。

下面这两道题,心里应该有数了吧,建议去看看相关源码哦。

Character ch1 = new Character('a');
Character ch2 = new Character('a');
Character ch3 = 'b';
Character ch4 = 'b';
char ch5 = 'b';
System.out.println(ch1.equals(ch2));
System.out.println(ch1 == ch2);
System.out.println(ch3.equals(ch4));
System.out.println(ch3 == ch4);
System.out.println(ch3.equals(ch5));
System.out.println(ch3 == ch5);
System.out.println(ch2.equals(ch3));
System.out.println(ch2 == ch3);
System.out.println(ch2.equals(ch5));
System.out.println(ch2 == ch5);

String s1 = "abc";
String s2 = "abc";
String s3 = new String("def");
String s4 = new String("def");
System.out.println(s1.equals(s2));
System.out.println(s1 == s2);
System.out.println(s3.equals(s4));
System.out.println(s3 == s4);

这里就不给出答案了,可以写程序检验一下。

最后总结一下 equals() 方法和双等号的区别:

  • equals 是一个方法,而双等号是一个运算符。
  • equals 方法的返回值要根据方法的具体的实现而定。
  • 对于基本数据类型来说,双等号是比较的数值,而对于类类型,双等号比较的是引用是否相同,这里需要注意 Java 中的缓存策略和常量池。
  • 如果在类中,没有对 equals() 方法重写,那么 equals() 方法是父类 Object 中的实现,比较的也是引用是否相同。

欢迎关注我的微信公众号:“编程心路”,在编程的路上,我们一起成长,回复任意关键字会有惊喜哦!


5763525-bee38c443b97ec3b.jpg
编程心路
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值