簡體   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