如何在 Java 中重写 equals 方法

我试图在 Java 中重写 equals 方法。我有一个类 People,它基本上有2个数据字段 nameage。现在我想覆盖 equals方法,这样我就可以在2个 People 对象之间进行检查。

我的代码如下

public boolean equals(People other){
boolean result;
if((other == null) || (getClass() != other.getClass())){
result = false;
} // end if
else{
People otherPeople = (People)other;
result = name.equals(other.name) &&  age.equals(other.age);
} // end else


return result;
} // end equals

但是当我写 age.equals(other.age)的时候,它会给我一个错误,因为 equals 方法只能比较 String,age 是 Integer。

解决方案

我使用 ==运算符的建议,我的问题得到了解决。

324461 次浏览

因为我猜 ageint型:

public boolean equals(Object other){
boolean result;
if((other == null) || (getClass() != other.getClass())){
result = false;
} // end if
else{
People otherPeople = (People)other;
result = name.equals(otherPeople.name) &&  age == otherPeople.age;
} // end else


return result;
} // end equals

我不确定细节,因为你还没有发布完整的代码,但是:

  • 还要记得覆盖 hashCode()
  • equals方法的参数类型应该是 Object,而不是 People。目前您正在重载而不是重写 equals 方法,这可能不是您想要的,特别是考虑到您稍后会检查它的类型。
  • 你可以使用 instanceof来检查它是一个 People 对象,例如 if (!(other instanceof People)) { result = false;}
  • equals用于所有对象,但不用于原语。我认为你的意思是年龄是一个 int(原语) ,在这种情况下只使用 ==。请注意,整数(大写“ I”)是一个对象,应该与等于。

有关详细信息,请参阅 在 Java 中重写 equals 和 hashCode 时应该考虑哪些问题?

如果 age 是 int,那么应该使用 = = ,如果是 Integer 对象,那么可以使用 equals ()。 如果重写 equals,还需要实现 hashcode 方法。合同的细节可以在对象的 javadoc 中找到,也可以在 web 的不同页面中找到。

引入一个改变参数类型的新方法签名称为 超载:

public boolean equals(People other){

这里 People不同于 Object

当一个方法签名与它的超类保持相同时,它被称为 重写,而 @Override注释有助于在编译时区分这两者:

@Override
public boolean equals(Object other){

如果没有看到 age的实际声明,就很难说出错误出现的原因。

//Written by K@stackoverflow
public class Main {


/**
* @param args the command line arguments
*/
public static void main(String[] args) {
// TODO code application logic here
ArrayList<Person> people = new ArrayList<Person>();
people.add(new Person("Subash Adhikari", 28));
people.add(new Person("K", 28));
people.add(new Person("StackOverflow", 4));
people.add(new Person("Subash Adhikari", 28));


for (int i = 0; i < people.size() - 1; i++) {
for (int y = i + 1; y <= people.size() - 1; y++) {
boolean check = people.get(i).equals(people.get(y));


System.out.println("-- " + people.get(i).getName() + " - VS - " + people.get(y).getName());
System.out.println(check);
}
}
}
}


//written by K@stackoverflow
public class Person {
private String name;
private int age;


public Person(String name, int age){
this.name = name;
this.age = age;
}


@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}


if (obj.getClass() != this.getClass()) {
return false;
}


final Person other = (Person) obj;
if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) {
return false;
}


if (this.age != other.age) {
return false;
}


return true;
}


@Override
public int hashCode() {
int hash = 3;
hash = 53 * hash + (this.name != null ? this.name.hashCode() : 0);
hash = 53 * hash + this.age;
return hash;
}


public int getAge() {
return age;
}


public void setAge(int age) {
this.age = age;
}


public String getName() {
return name;
}


public void setName(String name) {
this.name = name;
}
}

产出:

运行:

—— Subash Adhikari VS-K false

—— Subash Adhikari-VS-StackOverflow false

—— Subash Adhikari-VS-Subash Adhikari true

—— K-VS-StackOverflow false

—— K-VS-Subash Adhikari 假

—— StackOverflow-VS-Subash Adhikari false

——构建成功(总时间: 0秒)

@Override
public boolean equals(Object that){
if(this == that) return true;//if both of them points the same address in memory


if(!(that instanceof People)) return false; // if "that" is not a People or a childclass


People thatPeople = (People)that; // than we can cast it to People safely


return this.name.equals(thatPeople.name) && this.age == thatPeople.age;// if they have the same name and same age, then the 2 objects are equal unless they're pointing to different memory adresses
}

当比较 Java 中的对象时,你要做一个 语义检查,将对象的 类型和识别状态与:

  • 本身(同一实例)
  • 本身(克隆或重构的副本)
  • 其他不同类型的对象
  • 同类型的其他对象
  • null

规则:

  • 对称性 : a.equals(b) == b.equals(a)
  • equals()总是产生 truefalse,但从来没有一个 NullpointerExceptionClassCastException或任何其他投掷

比较:

  • 键入 check : 两个实例都需要是 一样类型,这意味着您必须比较实际的类以获得相等性。当开发人员使用 instanceof进行类型比较时(只有在没有子类的情况下才能正常工作,而且在 A extends B -> a instanceof b != b instanceof a)时违反了对称性规则) ,这通常是不正确实现的。
  • 标识状态的语义检查 : 确保您理解标识实例的状态。人们可以通过他们的社会保险号码来识别,但不能通过头发的颜色(可以染色)、姓名(可以改变)或年龄(随时变化)来识别。只有使用值对象才能比较完整状态(所有非瞬态字段) ,否则只检查标识实例的内容。

对于你的 Person课程:

public boolean equals(Object obj) {


// same instance
if (obj == this) {
return true;
}
// null
if (obj == null) {
return false;
}
// type
if (!getClass().equals(obj.getClass())) {
return false;
}
// cast and compare state
Person other = (Person) obj;
return Objects.equals(name, other.name) && Objects.equals(age, other.age);
}

可重用的通用实用工具类:

public final class Equals {


private Equals() {
// private constructor, no instances allowed
}


/**
* Convenience equals implementation, does the object equality, null and type checking, and comparison of the identifying state
*
* @param instance       object instance (where the equals() is implemented)
* @param other          other instance to compare to
* @param stateAccessors stateAccessors for state to compare, optional
* @param <T>            instance type
* @return true when equals, false otherwise
*/
public static <T> boolean as(T instance, Object other, Function<? super T, Object>... stateAccessors) {
if (instance == null) {
return other == null;
}
if (instance == other) {
return true;
}
if (other == null) {
return false;
}
if (!instance.getClass().equals(other.getClass())) {
return false;
}
if (stateAccessors == null) {
return true;
}
return Stream.of(stateAccessors).allMatch(s -> Objects.equals(s.apply(instance), s.apply((T) other)));
}
}

对于 Person类,使用以下实用工具类:

public boolean equals(Object obj) {
return Equals.as(this, obj, t -> t.name, t -> t.age);
}

第十条对等时遵守总合同

根据有效的 Java ,重写 equals方法看起来很简单,但是有很多方法会出错,而且后果可能很严重。避免问题的最简单方法是不重写 equals方法,在这种情况下,类的每个实例只等于它自己。如果适用下列任何条件,这是正确的做法:

  • 该类的每个实例本质上都是唯一的。对于像 Thread 这样表示活动实体而不是值的类来说,情况是这样的。Object 提供的 equals 实现具有适合这些类的正确行为。

  • 该类不需要提供“逻辑相等”测试。例如,java.util.regex。模式可以覆盖 equals 来检查两个模式实例是否表示完全相同的正则表达式,但是设计人员认为客户机不会需要或想要这个功能。在这些情况下,从 Object 继承的 equals 实现是理想的。

  • 超类已经覆盖了 equals, ,并且超类的行为适合于这个类。例如,大多数 Set 实现从 AbstractSet 继承它们的等于实现,从 AbstractList 继承 List 实现,从 AbstractMap 继承 Map 实现。

  • 该类是 private 或 package-private ,您可以确定永远不会调用它的 equals 方法。如果您非常规避风险,您可以重写 equals 方法,以确保它不会被意外调用:

equals方法实现了一个等价关系,它具有以下属性:

  • 自反: 对于任何非空引用值 xx.equals(x)必须返回 true。

  • 对称性: 对于任何非空引用值 xyx.equals(y)必须返回 true 当且仅当 y.equals (x)返回 true。

  • 传递性: 对于任何非空参考值 xyz,如果 x.equals(y)返回 true,而 y.equals(z)返回 true,那么 x.equals(z)必须返回 true

  • 一致性: 对于任何非空参考值 xyx.equals(y)的多个调用必须始终返回 true或始终返回 false,前提是不修改等于比较中使用的信息。

  • 对于任何非空引用值 xx.equals(null)必须返回 false

下面是一个高质量的 equals 方法的配方:

  1. 使用 ==操作符检查参数是否是对此对象的引用。如果是这样,返回 true。这只是一个性能优化,但是如果比较代价可能很高,那么这个优化是值得的。

  2. 使用 instanceof运算符检查参数的类型是否正确。否则,返回 false。通常,正确的类型是方法出现的类。有时,它是由这个类实现的某个接口。如果该类实现了一个接口,该接口改进了 equals 契约,以允许跨实现该接口的类进行比较,则使用该接口。集合接口,如 Set、 List、 Map 和 Map。条目具有此属性。

  3. 将参数转换为正确的类型。因为这个强制转换之前有一个 instanceof 测试,所以它肯定会成功。

  4. 对于类中的每个“重要”字段,检查参数的该字段是否与此对象的相应字段匹配。如果所有这些测试都成功,则返回 true; 否则返回 false。如果步骤2中的类型是接口,则必须通过接口方法访问参数的字段; 如果类型是类,则可以根据字段的可访问性直接访问这些字段。

  5. 对于类型不是 floatdouble的基元字段,使用 ==运算符进行比较; 对于对象引用字段,递归调用 equals方法; 对于 float字段,使用静态 Float.compare(float, float)方法; 对于 double字段,使用 Double.compare(double, double)。浮点和双字段的特殊处理是由于 Float.NaN-0.0f和类似的双值的存在所必需的; 虽然你可以将 floatdouble字段与静态方法 double2和 double3进行比较,但是每次比较都需要自动装箱,这样性能会很差。对于 double4字段,将这些准则应用于每个元素。如果数组字段中的每个元素都是有效的,请使用 double5方法之一。

  6. 一些对象引用字段可能合法地包含 null。为了避免出现 NullPointerException,请使用静态方法 Objects.equals(Object, Object)检查这些字段是否相等。

    // Class with a typical equals method
    
    
    public final class PhoneNumber {
    
    
    private final short areaCode, prefix, lineNum;
    
    
    public PhoneNumber(int areaCode, int prefix, int lineNum) {
    
    
    this.areaCode = rangeCheck(areaCode,  999, "area code");
    
    
    this.prefix   = rangeCheck(prefix,    999, "prefix");
    
    
    this.lineNum  = rangeCheck(lineNum,  9999, "line num");
    
    
    }
    
    
    private static short rangeCheck(int val, int max, String arg) {
    
    
    if (val < 0 || val > max)
    
    
    throw new IllegalArgumentException(arg + ": " + val);
    
    
    return (short) val;
    
    
    }
    
    
    @Override public boolean equals(Object o) {
    if (o == this)
    return true;
    if (!(o instanceof PhoneNumber))
    return false;
    PhoneNumber pn = (PhoneNumber)o;
    return pn.lineNum == lineNum && pn.prefix == prefix
    && pn.areaCode == areaCode;
    }
    ... // Remainder omitted
    
    
    }
    

下面是我最近使用的解决方案:

public class Test {
public String a;
public long b;
public Date c;
public String d;
    

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Test)) {
return false;
}
Test testOther = (Test) obj;
return (a != null ? a.equals(testOther.a) : testOther.a == null)
&& (b == testOther.b)
&& (c != null ? c.equals(testOther.c) : testOther.c == null)
&& (d != null ? d.equals(testOther.d) : testOther.d == null);
}


}

博士

record Person ( String name , int age ) {}
if(
new Person( "Carol" , 27 )              // Compiler auto-generates implicitly the constructor.
.equals(                                // Compiler auto-generates implicitly the `equals` method.
new Person( "Carol" , 42 )
)
)                                           // Returns `false`, as the name matches but the age differs.
{ … }

细节

虽然您的特定问题得到了解决(使用 ==进行 int基元值之间的相等性测试) ,但是还有一种替代方法可以消除编写该代码的需要。

record

Java16 带来了 记录特性。

记录是编写一个类的简短方法,该类的主要目的是透明和不变地传输数据。编译器隐式地创建构造函数、 getter、 equalshashCode以及 toString

自动提供的 equals方法

默认的隐式 equals方法比较为记录声明的每个成员字段。成员可以是对象或基元,这两种类型在默认的 equals方法中都会自动进行比较。

例如,如果有一条携带两个字段 nameagePerson记录,将自动比较这两个字段,以确定一对 Person对象之间的相等性。

public record Person ( String name , int age ) {}

试试看。

Person alice = new Person( "Alice" , 23 ) ;
Person alice2 = new Person( "Alice" , 23 ) ;
Person bob = new Person( "Bob" , 19 ) ;


boolean samePerson1 = alice.equals( alice2 ) ;  // true.
boolean samePerson2 = alice.equals( bob ) ;  // false.

如果希望获得默认行为以外的其他行为,则 可以重写记录上的 equals方法。但是如果覆盖 equals,一定要覆盖 hashCode以获得一致的逻辑,如 对于传统的 Java 类来说是这样的。另外,请三思: 无论何时向 record添加方法,都要重新考虑一下记录结构是否真的适合该问题域。

提示: record可以在其他类中定义,甚至可以在方法中本地定义。

对于懒惰的程序员来说: lombok库是非常容易和节省时间的。请看 < a href = “ https://projectlombok.org/”rel = “ nofollow noReferrer”> 这个链接 您只需要在 IDE 中应用这个库,然后只需@Data 就可以完成了,而不需要编写代码和规则行。

import lombok.Data;


@Data  // this is the magic word :D
public class pojo {


int price;
String currency;
String productName;


}

事实上,在上面的代码中,@Data 是

import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Getter
@Setter
@EqualsAndHashCode
@ToString
//or instead of all above @Data


public class pojo {


int price;
String currency;
String productName;


}