Lombok EqualsAndHashCode註解
一、概述
在本教程中,我們將討論 Lombok 的@EqualsAndHashCode註釋,它根據類的字段生成equals()和hashCode()方法。
2. @EqualsAndHashcode的用法
Lombok 中的@EqualsAndHashCode註解為給定類生成equals()和hashCode()方法,默認情況下使用所有非靜態和非瞬態字段。
要自定義使用的字段,我們可以使用@EqualsAndHashCode.Include標記它們或使用@EqualsAndHashCode.Exclude排除它們。
讓我們看一個例子來了解它是如何工作的:
@EqualsAndHashCode
public class Employee {
private String name;
private int id;
private int age;
}
在此示例中,我們定義了一個Employee類,其中包含字段name 、 id,和age,並使用@EqualsAndHashCode對其進行註釋。
讓我們看一下包含 Lombok 生成的equals() 、 hashCode()和canEqual()方法的編譯類:
public class EmployeeDelomboked {
private String name;
private int id;
private int age;
public boolean equals(Object o) {
if (o == this) {
return true;
} else if (!(o instanceof Employee)) {
return false;
} else {
Employee other = (Employee) o;
if (!other.canEqual(this)) {
return false;
} else if (this.getId() != other.getId()) {
return false;
} else if (this.getAge() != other.getAge()) {
return false;
} else {
Object this$name = this.getName();
Object other$name = other.getName();
if (this$name == null) {
if (other$name == null) {
return true;
}
} else if (this$name.equals(other$name)) {
return true;
}
return false;
}
}
}
protected boolean canEqual(Object other) {
return other instanceof Employee;
}
public int hashCode() {
final int PRIME = 59;
int result = 1;
result = result * PRIME + this.getId();
result = result * PRIME + this.getAge();
Object $name = this.getName();
result = result * PRIME + ($name == null ? 43 : $name.hashCode());
return result;
}
}
在生成equals()和hashCode()方法時,Lombok 還會生成一個canEqual()方法,用於檢查比較的對像是否與當前對像是同一類的實例。它確保equals()方法僅在對象屬於同一類時才進行比較。
3. 從EqualsAndHashCode中排除字段
Lombok 提供了幾個選項來從方法生成過程中排除特定字段。讓我們來看看這些選項。
3.1.使用@EqualsAndHashcode.Exclude在Field級別排除
我們可以使用@EqualsAndHashCode.Exclude註釋來指示 Lombok從equals()和hashCode()方法的生成中排除一個字段:
@EqualsAndHashCode
public class Employee {
private String name;
@EqualsAndHashCode.Exclude
private int id;
@EqualsAndHashCode.Exclude
private int age;
}
在此示例中,我們使用@EqualsAndHashCode.Exclude註釋從equals()和hashCode()方法生成中排除id和age字段。因此,Lombok 不會使用這些字段來生成方法:
public class EmployeeDelomboked {
//fields names
//standard getters and setters
//equals() implementation
@Override
public int hashCode() {
final int PRIME = 59;
int result = 1;
final Object $name = this.getName();
result = result * PRIME + ($name == null ? 43 : $name.hashCode());
return result;
}
}
我們可以看到生成的*hashCode()*方法只是使用了name字段。
3.2.在班級排除
我們還可以在類級別使用*@EqualsAndHashCode.Exclude*註解來排除字段:
@EqualsAndHashCode(exclude = {"id", "age"})
public class Employee {
private String name;
private int id;
private int age;
}
在此示例中,我們沒有直接使用@EqualsAndHashCode.Exclude註釋字段。相反,我們在類級別應用註釋並指定要排除的字段名稱。無論如何,生成的equals()和hashCode()方法將是相同的。
4.包括特定字段
現在我們已經了解瞭如何排除字段,讓我們深入研究在equals()和hashCode()方法的生成中包括特定字段。
4.1.包含在字段級別
要在生成equals()和hashCode()方法時包含特定字段,我們可以使用@EqualsAndHashCode.Include註釋。此註釋通常與**@EqualsAndHashCode註釋上的onlyExplicitlyIncluded=true**協同工作:
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Employee {
@EqualsAndHashCode.Include
private String name;
@EqualsAndHashCode.Include
private int id;
private int age;
}
我們使用@EqualsAndHashCode.Include設置onlyExplicitlyIncluded=true屬性和帶註釋的name和id字段。 Lombok 僅使用這些字段生成equals()和hashCode()方法。它排除了age字段,因為它沒有用@EqualsAndHashCode.Include標記。
4.2.在方法級別包含
此外,我們可以將自定義方法的結果合併到equals()和hashCode()方法中,方法是使用@EqualsAndHashCode.Include:
@EqualsAndHashCode
public class Employee {
private String name;
private int id;
private int age;
@EqualsAndHashCode.Include
public boolean hasOddId() {
return id % 2 != 0;
}
}
在這裡,我們用@EqualsAndHashCode.Include註解了hasOddId()方法,表明 Lombok 在生成equals()和hashCode()方法時,除了字段之外,還應該使用該方法的結果:
public class EmployeeDelomboked {
//fields, equals method
//standard getters and setters
public int hashCode() {
final int PRIME = 59;
int result = 1;
result = result * PRIME + this.getId();
result = result * PRIME + this.getAge();
result = result * PRIME + (this.hasOddId() ? 79 : 97);
final Object $name = this.getName();
result = result * PRIME + ($name == null ? 43 : $name.hashCode());
return result;
}
}
我們可以看到hashCode()方法包含hasOddId()方法。
4.3.包括在班級
@EqualsAndHashCode包含一個of來定義包含的字段。在這種情況下,我們不在字段或方法中使用任何註釋:
@EqualsAndHashCode(of = {"name", "id"})
public class Employee {
private String name;
private int id;
private int age;
}
在這裡,我們使用of屬性指定要包含在@EqualsAndHashCode註釋中的字段name和id 。通過這樣做,Lombok 將僅使用這些字段來生成equals()和hashCode()方法。
這種方法類似於使用@EqualsAndHashCode.Include和onlyExplicitlyIncluded屬性。 Lombok 實際上建議改用onlyExplicitlyIncluded 。 of屬性預計將在未來被棄用。
EqualsAndHashCode與Inheritance
當一個類擴展另一個類時,Lombok 的@EqualsAndHashCode生成方法默認不會調用父類的equals()和hashCode()方法。但是,我們可以通過將callSuper屬性設置為**true :** ,將父類的equals()和hashCode()包含在子類的equals()和hashCode()的生成中:
@EqualsAndHashCode(callSuper = true)
public class Manager extends Employee {
private String departmentName;
private int uid;
}
public class ManagerDelomboked extends Employee {
//fields
//equals implementation
public int hashCode() {
final int PRIME = 59;
int result = super.hashCode();
result = result * PRIME + this.getUid();
final Object $departmentName = this.getDepartmentName();
result = result * PRIME + ($departmentName == null ? 43 : $departmentName.hashCode());
return result;
}
}
在這裡,我們可以看到hashCode()方法調用了super.hashCode()方法。
6.龍目島@EqualsAndHashCode配置
要配置@EqualsAndHashCode註解的行為,我們需要在我們的項目中創建一個lombok.config文件。該文件包含配置鍵,可用於修改生成的equals()和hashCode()方法的行為。
6.1. lombok.equalsAndHashCode.doNotUseGetters
lombok.equalsAndHashCode.doNotUseGetters = [true | false] (default: false)
配置鍵lombok.equalsAndHashCode.doNotUseGetters可以設置為true或false (默認為false )。
當設置為true時,Lombok 在生成equals()和hashCode()方法時將直接訪問字段而不是使用 getter(如果可用)。但是,如果註釋參數doNotUseGetters被顯式指定,則它優先於此設置。
6.2. lombok.equalsAndHashCode.callSuper
lombok.equalsAndHashCode.callSuper = [call | skip | warn] (default: warn)
配置鍵lombok.equalsAndHashCode.callSuper可以設置為調用、 skip或warn (默認warn )。
如果設置為調用,Lombok 將在為子類生成這些方法時包括對hashCode()和equals()的超類實現的調用。如果設置為skip ,則不會生成此類調用。默認行為類似於skip ,但有一個額外的警告。
6.3. lombok.equalsAndHashCode.flagUsage
lombok.equalsAndHashCode.flagUsage = [warning | error] (default: not set)
如果已配置,Lombok 會將@EqualsAndHashCode的任何使用標記為警告或錯誤。
七、使用@EqualsAndHashCode註解的優缺點
使用@EqualsAndHashCode註解既有優點也有缺點。讓我們來看看其中的一些。
7.1.優點
Lombok 的@EqualsAndHashCode註釋是一種有用的技術,可用於簡化equals()和hashCode()方法的創建。它提供自定義選項,例如排除字段或使用不同的哈希碼算法。
7.2.缺點
使用 Lombok 的@EqualsAndHashCode註釋有幾個缺點。首先,當兩個對象具有不同的字段值但哈希碼相同時,它會產生不需要的哈希碼衝突。
其次,它可能與依賴equals()和hashCode()傳統實現的其他庫和框架不兼容。
最後,它會生成未在類中明確定義的方法,從而降低可讀性,使理解代碼邏輯變得更加困難。
8. 常見問題
在具有雙向關係(例如父子關係)的類上使用 Lombok 的@EqualsAndHashCode註解時,可能會出現java.lang.StackOverflowError 。
讓我們看一個@EqualsAndHashCode如何導致問題的例子:
@EqualsAndHashCode
public class Employee {
private String name;
private int id;
private int age;
private Manager manager;
}
@EqualsAndHashCode
public class Manager {
private String name;
private Employee assistantManager;
}
在此示例中, Employee和Manager類具有雙向關係並使用@EqualsAndHashCode 。當 Employee 的實例調用其hashCode()方法時,它將觸發對Manager類的hashCode()方法的調用,該方法將遞歸回調到Employee的hashCode()方法。這個遞歸循環將一直持續到程序用完堆棧空間並拋出錯誤為止。
為避免此問題,我們可以使用@EqualsAndHashCode的 exclude 或onlyExplicitlyIncluded屬性從hashCode()和equals()方法的生成中排除其中一個類。例如,我們可以從Employee類的hashCode()計算中排除manager字段:
@EqualsAndHashCode(exclude = "manager")
public class EmployeeV2 {
private String name;
private int id;
private int age;
private Manager manager;
}
9.結論
在本文中,我們了解了 Lombok @EqualsAndHashCode註釋如何生成equals()和hashCode()方法。我們還探討了字段和類級別的包含和排除如何自定義生成的方法。
使用 Lombok @EqualsAndHashCode註解可以省時省力。但是,我們應該注意潛在的問題,例如哈希碼衝突、與其他庫的不兼容以及雙向關係中的StackOverflowError 。
與往常一樣,可以在 GitHub 上找到本文的代碼。