equals和hashCode方法

一:前言

我们都知道这样一个知识:

  • equals方法用于比较的是对象的内容;
  • ==操作符用于比较的是对象的内存地址;
  • 比较字符串String是否相等用equals方法;

二:默认equals方法

public class User {
    private Long id;
    private String username;
    private Integer age;
}

public static void main(String[] args) {
    String str = "hello";
    String str2 = "hello";
    // true
    System.out.println(str.equals(str2));


    User user = new User(1L, "mengday");
    User user2 = new User(1L, "mengday");
    // false
    System.out.println(user == user2);
    // false
    System.out.println(user.equals(user2));
}

运行结果分析:

  • 字符串比较用equals,而equals是比较对象的内容,str和str2的内容一样,所以是true;
  • == 比较的是内存地址,因为user和user2两个内存地址不一样,所以比较结果为false;
  • equals用于比较对象的内容,user和user2的内容一样,那为啥是false呢,我们来看一下user的equals方法,User类中并没有自己的equals方法,而是集成的Object中的equals方法,通过查看源码得知Object中的equals方法默认是使用==来判断对象的内存地址是否一样,因user和user2的内存地址不一样,所以结果为false;
public class Object {
    /** 
    * 比较两个对象的内存地址是否相等
    */
    public boolean equals(Object obj) {
        return (this == obj);
    }
}

三:重写equals方法

如果想通过判断对象的字段值是否相等就需要重写equals方法,自定义是否相等的规则, 如:如果对象的id和对象的username都相同,那么我们就认为他们是同一个人,我们使用IDE来生成equals方法,为了避免出错,尽量不要手写。

public class User {
    /**
     * 如果两个对象的内存地址一样,那么这两个对象肯定是同一个东西
     * 如果对象为null、或者两个对象不是相同的Class,那么表示两个东西不相等
     * 如果两个对象指定的字段(id、username)的值都一样,那么这两个对象认为是同一个人
     */
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return Objects.equals(id, user.id) &&
                Objects.equals(username, user.username);
    }
}


public final class Objects {
    public static boolean equals(Object a, Object b) {
        return (a == b) || (a != null && a.equals(b));
    }
}    

经过重写之后再次运行System.out.println(user.equals(user2)); 结果为true

四:String#equals

我们自定了User的equals方法,再来看一下String类的equals方法,String已经重写了Object中的equals的默认实现,String类的equals方法是比较字符串中的每个字符来判断是否相等

public final class String implements Serializable, Comparable<String>, CharSequence {
    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 str = "hello";
String str2 = "hello";
// true
System.out.println(str.equals(str2));

五:List#contains(Object o)

再来看一下集合中的包含方法

public static void main(String[] args) {
    User user = new User(1L, "mengday");
    User user2 = new User(1L, "mengday");

    List<User> users = Arrays.asList(user);
    // true
    System.out.println(users.contains(user));
    // true
    System.out.println(users.contains(user2));
}

是否包含其实也就是判断两个对象是否相等,其实最终还是equals决定的。

  • users肯定包含user,因为users中的user元素和contains中的参数user本身就是同一个东西;
  • 我们并没有将user2添加到users集合中,但是结果也是true,通过源码注释我们可以看到contains方法实际上是调用equals方法;

因为我们重写了equals方法,我们认为只要id和username相同就是同一个人,因为user2和user对象的id和username都一样,所以为true

/**
* Returnstrueif this collection contains the specified element.
* More formally, returns true if and only if this collection
* contains at least one element e such that
* (o==null ? e==null : o.equals(e)).
boolean contains(Object o);

六:hashCode

一般重写equals方法都是通过ide来生成的,在生成的时候可以看到如果要生成equals同时也必须生成hashCode方法, 我们先将User中的equlas和hashCode方法先注释掉看另一个示例

public static void main(String[] args) {
    User user = new User(1L, "mengday");
    User user2 = new User(1L, "mengday");

    Map<User, String> userStringMap = new HashMap<>();
    userStringMap.put(user, "user");

    String value = userStringMap.get(user2);
    // null
    System.out.println(value);
}

这个示例Map中的key使用自定义对象User类,如果将User中的equlas和hashCode方法注释掉掉,我们通过get(key)方法永远都是null,如果重写了equals方法而没有同时重写hashCode方法,get返回也是null

当执行put方法时会调用hashCode(), 当执行get方法时会先执行hashCode再执行equals。

在java中,我们可以使用hashCode()来获取对象的哈希码,其值就是对象的存储地址,这个方法在Object类中声明,因此所有的子类都含有该方法。当我们调用Map的put方法或者get方法对Map容器进行操作时,都是根据键对象的哈希码来计算存储位置的。哈希码是通过对象来计算出一个数字,不同的对象计算不同的值。

当Map中使用自定义的对象作为Key需要同时重写了equals方法和hashCode方法,这样使用get时就能获取到值

public class User {
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return Objects.equals(id, user.id) &&
                Objects.equals(username, user.username);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, username);
    }
}    

为什么String可以作为Map中的key,因为String已经重写了hashCode方法

public final class String implements Serializable, Comparable<String>, CharSequence {
    private int hash; // Default to 0

    /**
    * String的hashCode是根据内容计算的,如果内容相同计算的结果也相同
    */
    public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char[] val = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }
}

猜你喜欢

转载自blog.csdn.net/vbirdbest/article/details/80314179