Java中equals()方法的使用
equals()方法的使用
-
可以使用在基本数据类型和引用数据类型中
-
如果比较的是基本数据类型,比较的是两个变量保存的数据是否相等。
*char = 10
此处是ASCII码值
如果比较的是引用数据类型,不记得是两个对象的地址值是否相同,即两个引用对象是否指向同一个对象实体
字符虽然相同,但是比较的是地址值,所以false :String str1 = new String("123"); String str2 = new String("123"); System.out.println(str1 == str2);//false
-
是一个方法,并非运算符
-
只适用于引用数据类型
-
Object
类中equals( )
的定义:public boolean equals(object obj) { return (this == obj); //this是指类的当前实例 }
说明:·类中定义的equals()和==的作用是相同的:比较两个对象的地址值是否相同。即两个引用是否指向同一个对象实体
-
对于像
String
、Data
、File
、包装类等都重写了Object
类中的equals
()方法,重写以后的方法比较的不是两个引用的地址,而是比较的两个对象的“实体内容”是否相同。 -
通常情况下,我们自定义的类如果使用equals(),通常是比较两个对象的“实体内容”是否相同,那么就需要对Object类中的equals()进行重写。
//此处是在Order类中 @Override public boolean equals(object obj){ if(this == obj){ return true; } if(obj instanceof Order){ Order order =(Order)obj; return this.orderId == order.orderId && this.orderName.equals(order.orderName); //基本数据类型用“==”, 引用数据类型用equals }
通常可以通过Generate自动生成自定义equals方法 macOS
快捷键control+return
如果类中只有一个属性,则直接可以
order.equals(order)
注意:
-
==
既可以比基本类型也可以比较引用类型。对于基本类型就是比较值,对于引用类型就是比较内存地址 -
equals
的话,它是属于java.lang.Object
类里面的方法,如果该方法没有被重写过默认也是==
;我们可以看到String
等类的equals
方法是被重写过的,而且String
类在日常开发中用的比较多,久而久之,形成了equals
是比较值的错误观点。 - 具体要看自定义类里有没有重写
Object
的equals
方法来判断。 - 通常情况下,重写
equals
方法,会比较类中的相应属性是否都相等。
String类的equals比较:
String str1 = new String("hello");
String str2 = new String("hello");
System.out.println(str1 == str2); // false (比较引用)
System.out.println(str1.equals(str2)); // true (比较内容)
自定义类的equals:
public class Student {
private String name;
// 没有重写equals,将使用Object类的equals方法(等同于==)
// 此时比较的是引用而不是内容
}
public class Student {
private String name;
// 重写equals,比较内容
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Student student = (Student) obj;
return Objects.equals(name, student.name);
}
}
常见类的equals行为:
// String - 比较字符串内容
String s1 = "hello";
String s2 = "hello";
s1.equals(s2); // true
// Integer - 比较数值
Integer i1 = new Integer(123);
Integer i2 = new Integer(123);
i1.equals(i2); // true
// ArrayList - 比较列表内容
ArrayList<String> list1 = new ArrayList<>(Arrays.asList("a", "b"));
ArrayList<String> list2 = new ArrayList<>(Arrays.asList("a", "b"));
list1.equals(list2); // true
== 与 equals 的区别:
String s1 = new String("hello");
String s2 = new String("hello");
String s3 = "hello";
String s4 = "hello";
System.out.println(s1 == s2); // false(不同对象引用)
System.out.println(s1.equals(s2)); // true(相同内容)
System.out.println(s3 == s4); // true(字符串常量池中的同一引用)
System.out.println(s3.equals(s4)); // true(相同内容)
重要提示:
-
如果自定义类需要比较内容,应该重写equals()方法
-
重写equals()时通常也需要重写hashCode()方法
-
Objects.equals()是空安全的比较方法,推荐使用
-
==比较基本类型时比较值,比较引用类型时比较引用地址
现在需要纠正上面代码示例存在的问题,这个很细节
在Java中重写equals方法时必须重写hashCode方法,这是因为这两个方法之间存在一个重要的契约关系。让我为您详细解释:
- equals和hashCode的基本契约:
- 如果两个对象通过equals()方法比较是相等的,那么它们的hashCode()方法必须返回相同的值
- 如果两个对象的hashCode()值不同,那么这两个对象一定不相等(equals()方法返回false)
- 如果两个对象的hashCode()值相同,这两个对象不一定相等(可能会发生哈希碰撞)
- 实际影响:
- 当我们使用HashMap、HashSet等基于哈希的集合类时,这个契约特别重要
- 这些集合类首先使用hashCode()来确定元素的存储位置
- 当发生哈希碰撞时,才会使用equals()方法进行进一步比较
- 如果违反契约会发生什么:
-
假设只重写equals而不重写hashCode:
- 两个对象通过equals()判断相等
- 但它们的hashCode()可能不同
- 在HashMap中会被存储到不同的位置
- 导致同一个对象可能会被存储多次
- 影响查找效率和正确性
- 示例:
class Person {
private String name;
private int age;
// 只重写equals
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Person)) return false;
Person person = (Person) obj;
return age == person.age &&
Objects.equals(name, person.name);
}
// 没有重写hashCode
// 将使用Object默认的hashCode实现
// 这会导致即使equals相等的对象也可能有不同的hash值
}
// 使用示例
Person p1 = new Person("张三", 25);
Person p2 = new Person("张三", 25);
// equals返回true
System.out.println(p1.equals(p2)); // true
HashSet<Person> set = new HashSet<>();
set.add(p1);
set.add(p2);
// 会存入两个对象,因为hashCode不同
System.out.println(set.size()); // 2
- 正确的做法:
class Person {
private String name;
private int age;
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Person)) return false;
Person person = (Person) obj;
return age == person.age &&
Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
- 最佳实践:
- 重写equals时必须重写hashCode
- hashCode方法应该使用对象的关键属性(用于equals比较的属性)
- 可以使用Objects.hash()方法来生成hashCode
- IDE通常可以自动生成这两个方法
- 如果类是不可变的,考虑缓存hashCode的值
总之,重写equals而不重写hashCode会违反Java集合框架的基本约定,可能导致基于哈希的集合类出现异常行为。为了保证程序的正确性和性能,这两个方法应该始终一起重写。
那现在有人会问了,前面的代码应该重写吗
public class Student { private String name; // 没有重写equals,将使用Object类的equals方法(等同于==) // 此时比较的是引用而不是内容 } public class Student { private String name; // 重写equals,比较内容 @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; Student student = (Student) obj; return Objects.equals(name, student.name); } }
这个问题很好!让我们分析两种情况:
第一种情况(没有重写equals):
public class Student {
private String name;
// 使用Object默认的equals方法
}
第二种情况(重写了equals):
public class Student {
private String name;
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Student student = (Student) obj;
return Objects.equals(name, student.name);
}
}
在这种情况下,必须重写hashCode方法。因为:
-
重写后的equals是基于name属性的内容比较
-
而默认的hashCode方法仍然基于对象的内存地址
-
这会导致两个name相同的Student对象:
- equals比较会返回true
- 但hashCode可能返回不同的值
-
违反了equals和hashCode的契约
正确的做法应该是:
public class Student {
private String name;
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Student student = (Student) obj;
return Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name);
}
}
实际影响示例:
// 使用没有重写hashCode的版本
Student s1 = new Student("张三");
Student s2 = new Student("张三");
System.out.println(s1.equals(s2)); // true
HashSet<Student> set = new HashSet<>();
set.add(s1);
set.add(s2);
System.out.println(set.size()); // 2,实际上应该是1
总结:
- 如果你使用Object默认的equals(比较引用),不需要重写hashCode
- 如果你重写了equals方法(比较内容),就必须重写hashCode
- 重写hashCode时,使用equals方法中用到的所有属性来计算哈希值
- 这样做是为了确保在使用HashSet、HashMap等基于哈希的集合类时能够正常工作