Java中equals和==的区别详述

概述

提及equals==的区别,大多数的回答==用于比较地址,equals用于比较内容。这个回答没有错,但涉及的角度过于片面和狭隘,接下来让我们看看equals==真正的区别所在。

1.概念

首先我们看一下二元比较运算符==的用法

对于基本数据类型,双等号用于比较二者的值是否相等。

int a = 1;
int b = 2;
System.out.println(a == b); // false

对于引用类型变量,双等号用于比较二者是否指向同一个对象。

Student john = new Student("john",19);
Student tomy = new Student("tomy",16);
System.out.println(john == tomy); // false

其次我们看一下Object类中equals方法的源码

public boolean equals(Object obj) {
        return (this == obj);
    }

看完源码后我们汇发现,耶( •̀ ω •́ )y,这是嘛呀!为什么equals的方法体中用的也是二元比较运算符==,这不是就跟双等号一样吗?没错,在Object类中equals==的作用是一模一样的,那既然作用都一样,equals==在Java中同时存在的意义在哪呢?

我们再看一下String类中的equals方法的源码

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

耶( •̀ ω •́ )y,这又是嘛呀!怎么这么多东西。没错,在String类中我们看到了equals的方法体要比Object类中多了一些内容,这是因为Java在String类中对equals进行了重写。其实在与大多数的类中都会和String类似的重写equals方法。所以,只有在重写了equals方法的类中,比较equals==的区别才有意义。

2.实例

接下来我们看几个例子

String a = "abc";
String b = "abc";
String c = new String("abc");
String d = new String("abc");
String e = new StringBuilder("abc").toString();
String f = new String("abc").intern();
String g = "ab" + "c";
String h = new String("ab") + new String("c");

System.out.println(a == b); // true
System.out.println(a == c); // false
System.out.println(a == e); // false
System.out.println(a == f); // true
System.out.println(a == g); // true
System.out.println(a == h); // false

怎么样?以上的结果和你们想象的结果一样吗?接下来让我们一起一一分析这些比较结果。

首先是字符串a和b比较的结果为true。有人可能会问,双等号不是比较两个对象是不是同一个对象么?字符串a和b为什么指向同一个对象呢?这里就要引入字符串常量池的概念了。为了提高效率,Java在定义字符串的时候并不会每次都生成一个新的字符串,而是在内存中分配一块区域作为常量池,每当遇到一个新定义的字符串变量时,会优先到常量池中找是否有相同字面量的字符串。如果有,就直接复用当前字符串,如果没有,才会去创建新的字符串对象。

我们再看字符串a和c比较的结果为false。这里会有一个疑问:不是每定义一个新的字符串变量就会优先在常量池中找相同字面量的字符串吗?a与c的字面量相同呀!在这里字符串变量c的创建用到了关键字new,它表示c显式地调用了String的构造器,这时字符串将无视常量池中是否含有相同字面量的字符串变量,强制重新创建一个字符串,新创建的字符串也不会加入常量池中,因此,a和c指向的不是同一个对象。

接下来看到字符串a和e比较的结果为false。e中使用了StringBuilder,字符串字面量"abc"作为StringBuilder的参数,按理说不会创建新的字符串呀。但问题出在后面的toString()方法上,想要把StringBuilder转换为String对象,就需要调用toString()方法。通过查看toString()方法的源码,我们发现在该方法中调用了String的构造器,因此和c一样,重新创建了字符串的对象。

@Override
 public String toString() {
     // Create a copy, don't share the array
     return new String(value, 0, count);
}

字符串a和f的比较结果比较有意思。f明明显式地调用了String的构造器,为什么比较结果为true呢?关键在于后面的intern()方法,这个方法大家可能不太熟悉,它的作用就是判断常量池中有没有对应的字符串,如果有的话,直接复用该字符串,如果没有的话,创建字符串并加入常量池中。所以,调用了intern()方法之后,效果和直接使用字面量是相同的。

字符串a和g的比较结果为true。字符串变量g创建时,将"ab""c"进行了拼接。Java在编译过程中,如果发现能够计算的结果,会在编译期直接将结果计算出来。也就是说在定义完字符串g的时候,编译完成时g的字面量已经是"abc"了。所以直接复用了a的引用,因此a与e指向的是同一个字符串对象。再来看a与h的比较结果为false,这是因为字符串h使用了String的构造器进行字符串拼接,由于需要创建对象,在编译期编译器无法直接对该结果进行求值,因此h只能等待运行期创建两个字符串对象,再进行拼接,并创建新的对象。

接下来还有几行的比较例子,看小伙伴们思考的结果是否正确。

System.out.println(c == d); // false
System.out.println(c == e); // false
System.out.println(c == f); // false
System.out.println(c == g); // false
System.out.println(c == h); // false

System.out.println(e == f); // false
System.out.println(e == g); // false
System.out.println(e == h); // false
System.out.println(f == g); // true
System.out.println(f == h); // false
System.out.println(g == h); // false

由于jdk1.7中将字符串的常量池由方法区移到堆中。使用双等号比较字符串是否指向同一对象时,使用jdk1.7之前和之后的版本比较结果可能有所不同。

3.包装类

Java中数据类型可以分为两类,一种的基本数据类型,一种是引用数据类型。基本数据类型的数据不是对象,所以对于要将数据类型作为对象来使用的情况,Java提供了相对应的包装类。
例如:int是基本数据类型,integer是引用数据类型,是int的包装类。

public final class Integer extends Number implements Comparable<Integer> 

Integerfinal类型的,表示不能被继承,同时实现了Number类,并实现了Comparable接口.


public static Integer valueOf(int i) {
        assert IntegerCache.high >= 127;
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

assertion语句的作用是对一个boolean表达式进行检查,一个正确程序必须保证这个boolean表达式的值为true;如果该值为false,说明程序已经处于不正确的状态下,系统将给出警告并且退出。一般来说,assertion用于保证程序最基本、关键的正确性。
Java内部为了节省内存,IntegerCache类中有一个数组缓存了值从-128~127Integer对象。当我们调用Integer.valueOf(int i)的时候,如果i的值时结余-128~127之间的,会直接从这个缓存中返回一个对象,否则就new一个新的Integer对象。

接下来我们来看一下包装类应该使用双等号还是equals进行比较。我们首先尝试像基本类型一样用双等号进行比较。

我们定义两个Integer的范围在-128~127之间,并且值相同的时候,用==比较值为true。当大于127或者小于-128的时候即使两个数值相同,也会new一个integer,那么比较的是两个对象,用==比较的时候返回false

Integer a = 1;
Integer b = 1;
Integer c = 129;
Integer d = 129;
System.out.println(a == b); // true
System.out.println(c == d); // false
System.out.println(a.equals(b)); // true
System.out.println(c.equals(d)); // true

其他和整型相关的包装类也存在-128~127之间的缓存,包括CharacterByteShortLong。但是缓存仅仅是为了提高效率的设计,并非为了进行比较。因此,对于包装类,我们应该使用equals来比较两个对象包装的值是否相等而并非==

4.自定义类

在使用自定义类时,双等号用于比较两个变量是否指向同一对象,而equals用于自定义规则的比较。如果我们想使用equals方法对两个自定义类的对象进行比较,就必须要重写equals方法,大多数情况下应该同时重写hashCode方法。下面还是以Student类为例,先看一下IDE自动生成的equals方法(这里使用了Objects工具类,Objects.equals方法实际上就是封装了对象的equals方法):

@Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        Student student = (Student) o;
        return age == student.age &&
                Objects.equals(name, student.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

可以看出,默认情况下IDE生成的代码是对所有字段分别进行比较,如果全部相等,则两个对象相等。我们可以进行修改,选择其中一部分字段进行比较。

5.总结

总结一下,双等号可以用于比较基本类型的值是否相等,或者用于比较引用类型的变量是否指向同一对象;equalsObject类中的方法,默认与==行为一致,可以通过重写该方法实现自定义规则的比较。常用的类中字符串和包装类应使用equals来比较其内容是否相同。自定义类中要想自定义规则比较不仅要重写equals方法还要重写 hashcode方法。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,509评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,806评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,875评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,441评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,488评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,365评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,190评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,062评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,500评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,706评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,834评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,559评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,167评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,779评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,912评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,958评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,779评论 2 354

推荐阅读更多精彩内容