简体   繁体   English

Java中如何重写equals方法

[英]How to override equals method in Java

I am trying to override equals method in Java.我正在尝试覆盖 Java 中的 equals 方法。 I have a class People which basically has 2 data fields name and age .我有一个类People ,它基本上有 2 个数据字段nameage Now I want to override equals method so that I can check between 2 People objects.现在我想重写equals方法,以便可以在 2 个 People 对象之间进行检查。

My code is as follows我的代码如下

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

But when I write age.equals(other.age) it gives me error as equals method can only compare String and age is Integer.但是当我写age.equals(other.age)时,它给了我错误,因为equals方法只能比较字符串并且年龄是整数。

Solution解决方案

I used == operator as suggested and my problem is solved.我按照建议使用了==运算符,我的问题得到了解决。

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

Output:输出:

run:跑:

-- Subash Adhikari - VS - K false -- Subash Adhikari - VS - K 假

-- Subash Adhikari - VS - StackOverflow false -- Subash Adhikari - VS - StackOverflow 错误

-- Subash Adhikari - VS - Subash Adhikari true -- Subash Adhikari - VS - Subash Adhikari 真实

-- K - VS - StackOverflow false -- K - VS - StackOverflow 错误

-- K - VS - Subash Adhikari false -- K - VS - Subash Adhikari 错误

-- StackOverflow - VS - Subash Adhikari false -- StackOverflow - VS - Subash Adhikari false

-- BUILD SUCCESSFUL (total time: 0 seconds) -- 构建成功(总时间:0 秒)

Introducing a new method signature that changes the parameter types is called overloading :引入改变参数类型的新方法签名称为重载

public boolean equals(People other){

Here People is different than Object .这里PeopleObject不同。

When a method signature remains the identical to that of its superclass, it is called overriding and the @Override annotation helps distinguish the two at compile-time:当方法签名与其超类的签名保持相同时,它被称为覆盖@Override注释有助于在编译时区分两者:

@Override
public boolean equals(Object other){

Without seeing the actual declaration of age , it is difficult to say why the error appears.没有看到age的实际声明,很难说为什么会出现错误。

I'm not sure of the details as you haven't posted the whole code, but:我不确定详细信息,因为您尚未发布整个代码,但是:

  • remember to override hashCode() as well记得也要覆盖hashCode()
  • the equals method should have Object , not People as its argument type. equals方法应该有Object ,而不是People作为它的参数类型。 At the moment you are overloading, not overriding, the equals method, which probably isn't what you want, especially given that you check its type later.目前您正在重载而不是覆盖 equals 方法,这可能不是您想要的,特别是考虑到您稍后检查它的类型。
  • you can use instanceof to check it is a People object eg if (!(other instanceof People)) { result = false;}您可以使用instanceof来检查它是否是 People 对象,例如if (!(other instanceof People)) { result = false;}
  • equals is used for all objects, but not primitives. equals用于所有对象,但不是基元。 I think you mean age is an int (primitive), in which case just use == .我认为您的意思是 age 是一个int (原始),在这种情况下只需使用== Note that an Integer (with a capital 'I') is an Object which should be compared with equals.请注意,整数(带有大写字母“I”)是应与等于进行比较的对象。

See What issues should be considered when overriding equals and hashCode in Java?请参阅在 Java 中覆盖 equals 和 hashCode 时应考虑哪些问题? for more details.更多细节。

Item 10: Obey the general contract when overriding equals第 10 项:在覆盖 equals 时遵守总合同

According to Effective Java , Overriding the equals method seems simple, but there are many ways to get it wrong, and consequences can be dire.根据 Effective Java ,覆盖equals方法似乎很简单,但有很多方法会出错,后果可能很可怕。 The easiest way to avoid problems is not to override the equals method, in which case each instance of the class is equal only to itself.避免问题的最简单方法是不要重写equals方法,在这种情况下,类的每个实例都只与自身相等。 This is the right thing to do if any of the following conditions apply:如果满足以下任何条件,这是正确的做法:

  • Each instance of the class is inherently unique .类的每个实例本质上都是唯一的 This is true for classes such as Thread that represent active entities rather than values.对于表示活动实体而不是值的类(例如 Thread)来说,情况确实如此。 The equals implementation provided by Object has exactly the right behavior for these classes. Object 提供的 equals 实现对这些类具有完全正确的行为。

  • There is no need for the class to provide a “logical equality” test.类不需要提供“逻辑相等”测试。 For example, java.util.regex.Pattern could have overridden equals to check whether two Pattern instances represented exactly the same regular expression, but the designers didn't think that clients would need or want this functionality.例如,java.util.regex.Pattern 可以覆盖 equals 来检查两个 Pattern 实例是否表示完全相同的正则表达式,但设计者认为客户不需要或不想要这个功能。 Under these circumstances, the equals implementation inherited from Object is ideal.在这种情况下,继承自 Object 的 equals 实现是理想的。

  • A superclass has already overridden equals, and the superclass behavior is appropriate for this class.超类已经覆盖了equals,并且超类的行为适合这个类。 For example, most Set implementations inherit their equals implementation from AbstractSet, List implementations from AbstractList, and Map implementations from AbstractMap.例如,大多数 Set 实现从 AbstractSet 继承其 equals 实现,从 AbstractList 继承 List 实现,从 AbstractMap 继承 Map 实现。

  • The class is private or package-private , and you are certain that its equals method will never be invoked.该类是 private 或 package-private ,并且您确定永远不会调用其 equals 方法。 If you are extremely risk-averse, you can override the equals method to ensure that it isn't invoked accidentally:如果你极度厌恶风险,你可以重写 equals 方法以确保它不会被意外调用:

The equals method implements an equivalence relation. equals方法实现了等价关系。 It has these properties:它具有以下属性:

  • Reflexive: For any non-null reference value x , x.equals(x) must return true.自反:对于任何非空引用值xx.equals(x)必须返回 true。

  • Symmetric: For any non-null reference values x and y , x.equals(y) must return true if and only if y.equals(x) returns true.对称:对于任何非空引用值xy ,当且仅当 y.equals(x) 返回 true 时, x.equals(y)必须返回 true。

  • Transitive: For any non-null reference values x , y , z , if x.equals(y) returns true and y.equals(z) returns true , then x.equals(z) must return true .传递:对于任何非空引用值xyz ,如果x.equals(y)返回true并且y.equals(z)返回true ,则x.equals(z)必须返回true

  • Consistent: For any non-null reference values x and y , multiple invocations of x.equals(y) must consistently return true or consistently return false , provided no information used in equals comparisons is modified.一致:对于任何非空引用值xyx.equals(y)的多次调用必须始终返回true或始终返回false ,前提是不修改 equals 比较中使用的信息。

  • For any non-null reference value x , x.equals(null) must return false .对于任何非空引用值xx.equals(null)必须返回false

Here's a recipe for a high-quality equals method:这是一个高质量的 equals 方法的秘诀:

  1. Use the == operator to check if the argument is a reference to this object.使用==运算符检查参数是否是对该对象的引用。 If so, return true.如果是,则返回 true。 This is just a performance optimization but one that is worth doing if the comparison is potentially expensive.这只是一种性能优化,但如果比较可能很昂贵,则值得这样做。

  2. Use the instanceof operator to check if the argument has the correct type.使用instanceof运算符检查参数是否具有正确的类型。 If not, return false.如果不是,则返回 false。 Typically, the correct type is the class in which the method occurs.通常,正确的类型是方法所在的类。 Occasionally, it is some interface implemented by this class.有时,它是这个类实现的一些接口。 Use an interface if the class implements an interface that refines the equals contract to permit comparisons across classes that implement the interface.如果类实现了一个接口,该接口改进了 equals 协定以允许在实现该接口的类之间进行比较,则使用一个接口。 Collection interfaces such as Set, List, Map, and Map.Entry have this property. Set、List、Map 和 Map.Entry 等集合接口具有此属性。

  3. Cast the argument to the correct type.将参数转换为正确的类型。 Because this cast was preceded by an instanceof test, it is guaranteed to succeed.因为这个转换之前有一个 instanceof 测试,所以保证成功。

  4. For each “significant” field in the class, check if that field of the argument matches the corresponding field of this object.对于类中的每个“重要”字段,检查参数的该字段是否与该对象的相应字段匹配。 If all these tests succeed, return true;如果所有这些测试都成功,则返回 true; otherwise, return false.否则,返回假。 If the type in Step 2 is an interface, you must access the argument's fields via interface methods;如果第 2 步中的类型是接口,则必须通过接口方法访问参数的字段; if the type is a class, you may be able to access the fields directly, depending on their accessibility.如果类型是类,您可以直接访问这些字段,具体取决于它们的可访问性。

  5. For primitive fields whose type is not float or double , use the == operator for comparisons;对于类型不是floatdouble的原始字段,使用==运算符进行比较; for object reference fields, call the equals method recursively;对于对象引用字段,递归调用equals方法; for float fields, use the static Float.compare(float, float) method;对于float字段,使用静态Float.compare(float, float)方法; and for double fields, use Double.compare(double, double) .对于double字段,请使用Double.compare(double, double) The special treatment of float and double fields is made necessary by the existence of Float.NaN , -0.0f and the analogous double values;由于Float.NaN-0.0f和类似的 double 值的存在,需要对 float 和 double 字段进行特殊处理; While you could compare float and double fields with the static methods Float.equals and Double.equals , this would entail autoboxing on every comparison, which would have poor performance.虽然您可以将floatdouble字段与静态方法Float.equalsDouble.equals进行比较,但这将需要对每次比较进行自动装箱,这会导致性能不佳。 For array fields, apply these guidelines to each element.对于array字段,将这些准则应用于每个元素。 If every element in an array field is significant, use one of the Arrays.equals methods.如果数组字段中的每个元素都很重要,请使用Arrays.equals方法之一。

  6. Some object reference fields may legitimately contain null .一些对象引用字段可能合法地包含null To avoid the possibility of a NullPointerException , check such fields for equality using the static method Objects.equals(Object, Object) .为避免出现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
}

When comparing objects in Java, you make a semantic check , comparing the type and identifying state of the objects to:在 Java 中比较对象时,您会进行语义检查,将对象的类型和标识状态与以下内容进行比较:

  • itself (same instance)本身(同一个实例)
  • itself (clone, or reconstructed copy)本身(克隆或重建副本)
  • other objects of different types其他不同类型的对象
  • other objects of the same type相同类型的其他对象
  • null

Rules:规则:

  • Symmetry : a.equals(b) == b.equals(a)对称性a.equals(b) == b.equals(a)
  • equals() always yields true or false , but never a NullpointerException , ClassCastException or any other throwable equals()总是产生truefalse ,但绝不会产生NullpointerExceptionClassCastException或任何其他可抛出的

Comparison:比较:

  • Type check : both instances need to be of the same type, meaning you have to compare the actual classes for equality.类型检查:两个实例必须是相同的类型,这意味着您必须比较实际的类是否相等。 This is often not correctly implemented, when developers use instanceof for type comparison (which only works as long as there are no subclasses, and violates the symmetry rule when A extends B -> a instanceof b != b instanceof a) .当开发人员使用instanceof进行类型比较时,这通常没有正确实现(只有在没有子类的情况下才有效,并且在A extends B -> a instanceof b != b instanceof a)
  • Semantic check of identifying state : Make sure you understand by which state the instances are identified.识别状态的语义检查:确保您了解识别实例的状态。 Persons may be identified by their social security number, but not by hair color (can be dyed), name (can be changed) or age (changes all the time).一个人可以通过他们的社会安全号码来识别,但不能通过头发颜色(可以染色)、姓名(可以更改)或年龄(一直在变化)来识别。 Only with value objects should you compare the full state (all non-transient fields), otherwise check only what identifies the instance.只有使用值对象才能比较完整状态(所有非瞬态字段),否则仅检查标识实例的内容。

For your Person class:对于您的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);
}

Reusable, generic utility class:可重用的通用实用程序类:

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

For your Person class, using this utility class:对于您的Person类,使用此实用程序类:

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

Since I'm guessing age is of type int :因为我猜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;dr 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.
{ … }

Details细节

While your specific problem is solved (using == for equality test between int primitive values), there is an alternative that eliminates the need to write that code.虽然您的特定问题已解决(使用==进行int原始值之间的相等性测试),但还有一种替代方法可以消除编写该代码的需要。

record

Java 16 brings the record feature. Java 16带来了记录功能。

A record is a brief way to write a class whose main purpose is to transparently and immutably carry data.记录是编写类的一种简短方式,其主要目的是透明且不可变地携带数据。 The compiler implicitly creates the constructor, getters, equals & hashCode , and toString .编译器隐式创建构造函数、getter、 equals & hashCodetoString

equals method provided automatically自动提供equals方法

The default implicit equals method compares each and every member field that you declared for the record.默认的隐式equals方法比较您为记录声明的每个成员字段。 The members can be objects or primitives, both types are automatically compared in the default equals method.成员可以是对象或基元,两种类型在默认的equals方法中自动比较。

For example, if you have a Person record carrying two fields, name & age , both of those fields are automatically compared to determine equality between a pair of Person objects.例如,如果您的Person记录带有两个字段nameage ,则会自动比较这两个字段以确定一对Person对象之间的相等性。

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

Try it.试试看。

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.

You can override the equals method on a record, if you want a behavior other than the default.如果您想要默认行为以外的行为,您可以覆盖记录上的equals方法。 But if you do override equals , be sure to override hashCode for consistent logic, as you would for a conventional Java class .但是,如果您确实覆盖了equals ,请务必覆盖hashCode以获得一致的逻辑,就像您对传统的 Java 类一样。 And, think twice: Whenever adding methods to a record , reconsider if a record structure is really appropriate to that problem domain.并且,三思而后行:每当向record添加方法时,请重新考虑记录结构是否真的适合该问题域。

Tip: A record can be defined within another class, and even locally within a method.提示:可以在另一个类中定义record ,甚至可以在方法中本地定义。

if age is int you should use == if it is Integer object then you can use equals().如果年龄是整数,你应该使用 == 如果它是整数对象,那么你可以使用 equals()。 You also need to implement hashcode method if you override equals.如果覆盖 equals,还需要实现 hashcode 方法。 Details of the contract is available in the javadoc of Object and also at various pages in web.合同的详细信息可在 Object 的 javadoc 以及 web 的各个页面中找到。

Here is the solution that I recently used:这是我最近使用的解决方案:

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

}

For lazy programmers: lombok library is very easy and time saving.对于懒惰的程序员: lombok库非常简单且省时。 please have a look at this link instead of writing lines of codes and rules, you just need to apply this library in your IDE and then just @Data and it is Done.请查看此链接,而不是编写代码和规则行,您只需在 IDE 中应用此库,然后只需 @Data 即可完成。

import lombok.Data;

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

int price;
String currency;
String productName; 

}

in fact in the above code, @Data is a shortcut for实际上在上面的代码中,@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