前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >你不知道的 equals 和 ==

你不知道的 equals 和 ==

作者头像
Wizey
发布2018-08-30 09:53:36
4830
发布2018-08-30 09:53:36
举报
文章被收录于专栏:编程心路编程心路

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

代码语言:javascript
复制
下面这段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,代码如下:

代码语言:javascript
复制
public class Test {
    public static void main(String[] args) {
        Integer i1 = 10;
        int i2 = i1;
    }
}

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

{% asset_img 反编译.png 反编译%}

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

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

代码语言:javascript
复制
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,现在大家应该都懂了。

代码语言:javascript
复制
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() 方法的源码。

代码语言:javascript
复制
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。

代码语言:javascript
复制
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 源码时,可能觉得有的地方写的“不好”,因为老外写的嘛,老外也有他们的编程风格,命名习惯,我们要从中吸取好的营养。

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

代码语言:javascript
复制
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 中的实现,比较的也是引用是否相同。
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018.08.17 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档