Java_语法基础_equals方法与“==”的区别

来源:互联网 发布:大唐麻将辅助软件 编辑:程序博客网 时间:2024/06/05 08:19

例:

package deep;public class Box {    private int batch;    public Box(int batch) {        this.batch = batch;    }    public static void main(String[] args) {        Box box1 = new Box(1);        Box box2 = new Box(1);        System.out.println(box1.equals(box2));        System.out.println(box1 == box2);    }}

运行结果:
false
false

“==”运算符比较两个Box对象(box1与box2),返回false,这个是在意料之中的,因为box1与box2是两个不同的对象,因而地址也不会相同。程序的原意是,如果两个盒子的生产批次(batch)相等,就认为是相同的盒子,否则是不同的盒子。然而,尽管box1与box2两个对象的批次相同,但是结果却事与愿违,equals方法同样返回了false。
请再看下面的例子:

package deep;public class StringEquals {    public static void main(String[] args) {        String s1 = new String("abc");        String s2 = new String("abc");        StringBuilder builder1 = new StringBuilder("abc");        StringBuilder builder2 = new StringBuilder("abc");        StringBuffer buffer1 = new StringBuffer("abc");        StringBuffer buffer2 = new StringBuffer("abc");        System.out.println(s1.equals(s2));        System.out.println(builder1.equals(builder2));        System.out.println(buffer1.equals(buffer2));    }}

运行结果:
true
false
false

这又是因为什么呢?
出现这种现象的原因是:我们没有弄清楚equals方法到底比较的是什么。从其根源来看,equals方法是在Object类中声明的,访问修饰符为public,而所有类(除Object类自身外)都是Object的直接或间接子类,也就是所有子类都继承了这个方法。在Object类中,equals方法实现如下:
public boolean equals(Object obj){
return (this==obj);
}
从而可知,在Object类中,equals方法与“==”运算符是完全等价的,而我们编写的Box类继承了Object类中的equals方法,因此,Box类中equals方法与“==”是等价的,也就是比较的是对象的地址,而非对象的内容。对于String类,之所以该类可以比较对象的内容,那是因为String类重写了eruals方法,使该方法比较的是字符序列(也就是我们通常所说的内容),而非对象的地址。而对于StringBuilder与StringBuffer两个类,与Box类相同,没有重写equals方法,故不同的对象,equals方法返回值为false。
在Box类中,我们也可以重写从Object类中继承的equals方法,从而来实现之前期望的结果,在Box类中增加如下代码:

    @Override    public boolean equals(Object obj) {        if (obj instanceof Box) {            Box box = (Box) obj;            return batch == box.batch;        }        return false;    }

equals的重写规则

我们在重写equals方法的时候,还是需要注意一些规则的。在JavaAPI文档中,有如下几点要求。
1.自反性。对于任何非null的引用值x,x.equals(x)应返回true。
2.对称性。对于任何非null的引用值x与y,当且仅当:y.equals(x)返回true时,x.equals(y)才应返回true。
3.传递性。对于任何非null的引用值x、y与z。如果x.equals(y)返回true,并且y.equals(z)返回true,那么x.equals(z)也应返回true。
4.一致性。对于任何非空引用值x与y,假设对象上equals比较中的信息没有被修改,则多次调用x.equals(y)始终返回true或始终返回false。
5.对于任何非空引用值x,x.equals(null)应返回false。

蝴蝶效应

目前我们已经知道,重写equals方法需要注意的地方。可是,是否重写了equals方法,使其具备上面所谈的几点要求就万事OK了呢?不是的,一旦涉及映射相关的操作(Map接口),还是会存在问题。
这是因为实现了Map接口的类会用到键对象(作为Key的对象)的哈希码,当调用put方法加入键值对或调用get方法取回值对象的时候,都是根据键对象的哈希码来计算存储位置的。如果对象的哈希码没有相关保证,就不能够得到预期的结果。
可以调用hashCode方法来获得对象哈希码,该方法在Object类中声明,所有子类都会继承,如下:
public native int hashCode();

在JavaAPI文档中,关于hashCode方法有以下几点规定。
1.在Java应用程序执行期间,如果在对象equals方法比较中所用的信息没有被修改过,那么在同一对象上多次调用hashCode方法时,必须一致地返回相同的整数。但如果多次执行同一个应用时,不要求该整数必须相同。
2.如果两个对象通过调用equals方法是相等的,那么这两个对象调用hashCode方法必须返回相同的整数。
3.如果两个对象通过调用equals方法是不相等的,不要求这两个对象调用hashCode方法必须返回不同的整数。但是,程序员应该意识到对不同的对象产生不同的哈希码值可以提高哈希表的性能。
如果一个类重写了equals方法,但是没有重写hashCode方法,会发生怎样的情况呢?那就会直接违背第2条规定,从而导致严重的后果。
例:

package deep;import java.util.HashMap;import java.util.Map;public class AbnormalResult {    public static void main(String[] args) {        Map<String, Value> map1 = new HashMap<String, Value>();        String s1 = new String("key");        String s2 = new String("key");        Value value = new Value(15);        map1.put(s1, value);        System.out.println("s1.equals(s2):" + s1.equals(s2));        System.out.println("map.get(s1):" + map1.get(s1));        System.out.println("map.get(s2):" + map1.get(s2));        Map<Key, Value> map2 = new HashMap<Key, Value>();        Key k1 = new Key("A");        Key k2 = new Key("A");        map2.put(k1, value);        System.out.println("k1.equals(k2):" + k1.equals(k2));        System.out.println("map.get(k1):" + map2.get(k1));        System.out.println("map.get(k2):" + map2.get(k2));    }}class Key {    private String k;    public Key(String k) {        this.k = k;    }    @Override    public boolean equals(Object obj) {        if (obj instanceof Key) {            Key key = (Key) obj;            return k.equals(key.k);        }        return false;    }}class Value {    private int v;    public Value(int v) {        this.v = v;    }    @Override    public String toString() {        return "Value [v=" + v + "]";    }}

运行结果:
s1.equals(s2):true
map.get(s1):Value [v=15]
map.get(s2):Value [v=15]
k1.equals(k2):true
map.get(k1):Value [v=15]
map.get(k2):null

map1加入(s1,value)键值对,然后使用键s1取出值value,这肯定是没问题的。由于s1与s2是相等的,所以,通过键s2也可以取出值来。
但是,尽管k1与k2也是相等的,可是当map2加入(k1,value)键值对时,通过k2却不能够取出value值。String类与我们自定义的Key有什么不同吗?Key类也重写了equals方法,并且也遵守了equals方法的规则,为什么还会如此呢?
根本原因就在于,HashMap类的get方法除了使用equals方法比较两个对象之外,还使用了hashCode方法进行比较,而Key类在重写了equals方法的同时,并没有重写hashCode方法,Key类使用的是从Object类继承的hashCode方法,该方法是通过对象地址来计算哈希码的,不同的对象哈希码也不会相同。如此一来,通过equals比较相等的两个对象k1和k2产生不同的哈希码,调用put方法加入(k1,value)键值对时,使用k1的哈希码计算存储地址,而调用get方法取出对象时,使用k2的哈希码计算存储地址,取得的值对象自然也就为null了。
与equals方法相似,String类在重写了equals方法的同时,也重写了hashCode方法。因而程序中对s1和s2的操作没有什么问题。所以,请记住这样一条结论:如果一个类重写了equals方法,那么该类也一定要重写hashCode方法。根据上面重写hashCode方法的3条规定,我们在重写该方法时,通常是equals方法中哪几个成员变量参与了相等的比较,就使用哪几个成员变量进行运算(运算规则根据需要自己来定),然后返回运算的结果。
回到我们上面的程序,要修正这个问题,只要在Key类中加入一个合适的hashCode方法就可以了,如下:

    @Override    public int hashCode() {        return k.hashCode();    }

要点总结:

  • 从Object类继承的equals方法与“==”运算符的比较方式是相同的。如果继承的equals方法对我们自定义的类不适用,则可以重写equals方法。
  • 重写equals方法的时候,需要遵守API文档中的5点规定,否则该类与其他类(例如实现了Collection接口或其子接口的类)交互时,很可能产生不确定的运行结果。
  • 再重写equals方法的同时,也必须要重写hashCode方法。否则该类与其他类(例如实现了Map接口及其子接口的类)交互时,很可能产生不确定的运行结果。
  • 重写hashCode方法时也要遵守API文档中的3点规定,其中第3点规定是建议性的。
0 0
原创粉丝点击