一:前言
我们都知道这样一个知识:
- 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;
}
}