繁体   English   中英

Java中如何重写equals方法

[英]How to override equals method in Java

我正在尝试覆盖 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方法只能比较字符串并且年龄是整数。

解决方案

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

//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 假

-- Subash Adhikari - VS - StackOverflow 错误

-- Subash Adhikari - VS - Subash Adhikari 真实

-- K - VS - StackOverflow 错误

-- K - VS - Subash Adhikari 错误

-- StackOverflow - VS - Subash Adhikari false

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

引入改变参数类型的新方法签名称为重载

public boolean equals(People other){

这里PeopleObject不同。

当方法签名与其超类的签名保持相同时,它被称为覆盖@Override注释有助于在编译时区分两者:

@Override
public boolean equals(Object other){

没有看到age的实际声明,很难说为什么会出现错误。

我不确定详细信息,因为您尚未发布整个代码,但是:

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

请参阅在 Java 中覆盖 equals 和 hashCode 时应考虑哪些问题? 更多细节。

第 10 项:在覆盖 equals 时遵守总合同

根据 Effective Java ,覆盖equals方法似乎很简单,但有很多方法会出错,后果可能很可怕。 避免问题的最简单方法是不要重写equals方法,在这种情况下,类的每个实例都只与自身相等。 如果满足以下任何条件,这是正确的做法:

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

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

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

  • 该类是 private 或 package-private ,并且您确定永远不会调用其 equals 方法。 如果你极度厌恶风险,你可以重写 equals 方法以确保它不会被意外调用:

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

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

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

  • 传递:对于任何非空引用值xyz ,如果x.equals(y)返回true并且y.equals(z)返回true ,则x.equals(z)必须返回true

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

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

这是一个高质量的 equals 方法的秘诀:

  1. 使用==运算符检查参数是否是对该对象的引用。 如果是,则返回 true。 这只是一种性能优化,但如果比较可能很昂贵,则值得这样做。

  2. 使用instanceof运算符检查参数是否具有正确的类型。 如果不是,则返回 false。 通常,正确的类型是方法所在的类。 有时,它是这个类实现的一些接口。 如果类实现了一个接口,该接口改进了 equals 协定以允许在实现该接口的类之间进行比较,则使用一个接口。 Set、List、Map 和 Map.Entry 等集合接口具有此属性。

  3. 将参数转换为正确的类型。 因为这个转换之前有一个 instanceof 测试,所以保证成功。

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

  5. 对于类型不是floatdouble的原始字段,使用==运算符进行比较; 对于对象引用字段,递归调用equals方法; 对于float字段,使用静态Float.compare(float, float)方法; 对于double字段,请使用Double.compare(double, double) 由于Float.NaN-0.0f和类似的 double 值的存在,需要对 float 和 double 字段进行特殊处理; 虽然您可以将floatdouble字段与静态方法Float.equalsDouble.equals进行比较,但这将需要对每次比较进行自动装箱,这会导致性能不佳。 对于array字段,将这些准则应用于每个元素。 如果数组字段中的每个元素都很重要,请使用Arrays.equals方法之一。

  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 }
@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或任何其他可抛出的

比较:

  • 类型检查:两个实例必须是相同的类型,这意味着您必须比较实际的类是否相等。 当开发人员使用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);
}

因为我猜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

tl;博士

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

Java 16带来了记录功能。

记录是编写类的一种简短方式,其主要目的是透明且不可变地携带数据。 编译器隐式创建构造函数、getter、 equals & hashCodetoString

自动提供equals方法

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

例如,如果您的Person记录带有两个字段nameage ,则会自动比较这两个字段以确定一对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 ,甚至可以在方法中本地定义。

如果年龄是整数,你应该使用 == 如果它是整数对象,那么你可以使用 equals()。 如果覆盖 equals,还需要实现 hashcode 方法。 合同的详细信息可在 Object 的 javadoc 以及 web 的各个页面中找到。

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

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);
    }

}

对于懒惰的程序员: lombok库非常简单且省时。 请查看此链接,而不是编写代码和规则行,您只需在 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;

}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM