Object.equals()与==

本文探讨equals()与'=='在Java中的区别,重点讲解Object.equals()源码、String类的equals重写,以及为何重写equals与hashCode的关系。通过实例揭示重写equals的意义和正确使用规则,包括一致性、自反性和hashCode的配合应用。

.equals()与"=="

从本质上来说,其实.equals()与"=="并无区别。equalsObject类的方法,而“==”为java语言的基本运算符,它们对于基本数据类型的变量比较的都是它们的值,对于引用类型的对象比较的则都是指向对象的地址。

1.Object.equals()源码

/*
* Params:obj – the reference object with which to compare.
* Returns:true if this object is the same as the obj argument; false otherwise.
* See Also:hashCode(), java.util.HashMap
*/
public boolean equals(Object obj) {
    return (this == obj);
}

可以看出,equals底层其实就是“”,所以原生的Object.equals()与“”没有任何区别,而我们所遇到的不同用法都是类重写了equals()方法。

2.String重写equals()方法

在我们的日常使用中,最常见的应该就是String类对equals的重写,源码如下

/*
*Params:anObject – The object to compare this String against
*Returns:true if the given object represents a String equivalent to this   string, false otherwise
*See Also:compareTo(String), equalsIgnoreCase(String)
*/
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;
}

可以看出String中,首先比较的是其指向的地址是否相同,相同则返回true。

Stringnew一个对象时,会先去常量池中寻找有没有相同的字符串,如果有则只在堆中创建一个String对象,如果没有则在常量池中创建一个新的字符串常量。String.intern()方法的调用时,如果常量池中含有该字符串,则返回在该常量池中的引用;如果不存在,则在常量池中创建字符串,并返回该字符串引用。

地址不相同时,则将对象中字符串的值进行比较。

instanceofjava的保留关键字,用以判断引用类型是否相同(基本数据无法使用),返回truefalse

public class InsatanceOf {
    public static void main(String[] args) {
        
        Father1 father = new Father1();
        Son1 son = new Son1();
        Son[] sons = new Son[7];
        
        System.out.println(son instanceof Father1); //true
        System.out.println(father instanceof Son1); //false
        System.out.println(sons instanceof Father[]); //true
        System.out.println(sons instanceof Son[]); //true
    }
}
class Father1{}
class Son1 extends Father1{}

instanceof常用于强制转换判断

Father1 convert (Son1 son1){
    if (son1 instanceof Father1){
        return (Father1) son1;
    }else {
        return null;
    }
}

3.重写equals的意义

在日常开发中,经常会需要重写equals方法,例如判断当对象不同时,判断对象内属性值是否相同,但一个一个判断又十分繁琐,这时在类中,重写equals方法就显得极为重要。

public class Student{
    private String name;
    private String age;
    private String sex;

    Student(String name, String age, String sex){
        this.name = name;
        this.age = age;
        this.sex = sex;
    }

    public boolean equals(Object obj){
        if (this == obj){
            return true;
        }
        if (obj instanceof Student){
            if (this.age.equals(((Student) obj).age) && this.name.equals(((Student) obj).name) && this.sex.equals(((Student) obj).sex)){
                return true;
            }
        }
        return false;
    }
}
class test {
    public static void main(String[] args) {
        Student student1 = new Student("李四", "18", "男");
        Student student2 = new Student("李四", "18", "男");
        Student student3 = new Student("张三", "18", "男");

        System.out.println(student1.equals(student2)); //true
        System.out.println(student1.equals(student3)); //false
    }
}

如上例所示,我们在Student类中重写了equals方法,在判断对象内容是否相同时,就快了很多,减少了代码冗余。

4.重写.equals()与重写hashCode

重写equals,自然要提到hashcode,且重写equals方法,最好也重写hashcode方法。

equalshashCode的规则

《Effective Java》中提到对equalshashCode的规则

对于euqals应该遵守如下约定:

1、自反性:x.equals(x) 必须为true
2、对称性:如果x.equals(y),则y.euqals(x)必须为true
3、传递性:如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,那么z.equals(x)也应该返回是“true”
4、一致性:如果x.equals(y)返回是“true”,只要x和y内容一直不变,不管你重复x.equals(y)多少次,返回都是“true”
5、任何情况下,x.equals(null),永远返回是“false”;x.equals(和x不同类型的对象)永远返回是“false”
6、覆盖equals时总是要覆盖hashCode

对于hashCode应该遵守如下约定:

1.在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是将对象进行 equals 比较时所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。
2.如果根据 equals(Object) 方法,两个对象是相等的,那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。
3.如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode 方法不 要求一定生成不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能。

​ 也就是说,hashCode()和``equals()保持一致,如果equals方法返回true,那么两个对象的hasCode()返回值必须一样。如果equals方法返回falsehashCode`可以不一样,但是这样不利于哈希表的性能,一般我们也不要这样做。

​ 当我们重写了equals方法,并创建了两个对象,判断相等返回true的条件是属性值相等。但如果我们没有重写hashCode方法,判断equals时会默认使用Object类的hashCode方法,这样会导致属性相等的两个对象得到的hashCode却不相同,这就违背了equals的使用规则。

光说没用上代码

java中,比较常用的hashMaphashSet就是采用上述思想,那么为什么要重写hashCode方法呢,我们继续使用上面的student类作说明

class test2{
    public static void main(String[] args) {
        Student student1 = new Student("李四", "18", "男");
        Student student2 = new Student("李四", "18", "男");

        HashSet<Student> set = new HashSet<>();
        set.add(student1);
        set.add(student2);
        System.out.println(set.size()); // 2
    }
}

我们都知道,hashSet是不允许重复的,然而两个相同的Student对象却同时放入了set中,这是为什么呢?答案是,未重写hashCode方法导致,hashCode值是默认的Object类的hashCode方法计算得到,这也就促使程序判断该对象在set中的位置是不存在的,所以也就存入了两个相等的对象。

hashSethashMap的数据结构为散列表(数组+链表/红黑树的形式),在查询某个元素x时,首先,通过hashCode方法得到其hash值,再通过hash值计算出其在散列表中的数组下标位置,如果该位置上没有元素,则说明散列表中没有元素x,如果该位置上有元素(如下标1),则x会依次与该位置上的链表存放元素依次进行比较,若其中一个相等,则说明散列表中有元素x。

这里我们重写StudenthashCode方法

@Override
public int hashCode() {
    System.out.println("调用了hashCode方法...");

    int hashResult = 1;
    hashResult = (hashResult + age.hashCode() + name.hashCode() + sex.hashCode()) * 99;
    System.out.println("name:"+name +" hashCode:" + hashResult);

    return hashResult;
}

控制台输出结果如下:

调用了hashCode方法...
name:李四 hashCode:86490756
调用了hashCode方法...
name:李四 hashCode:86490756
1

综上,hashMaphashSet比较元素时,会先比较hashCode,只有当hashCode相等时,才会进一步比较元素属性或值是否相等。所以重写对象,一定要重写hashCode

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值