那些年匆匆而過的磚-lombok 破曉篇
Lombok總算整明白了 -- 破曉篇
使用問題
注: 本文所講述的lombok 版本是 v1.18.17
lombok也用了幾個月, 以為老司機清楚熟路, 誰知陰溝裡帆船 話不多說看一個筆者最近遇到的小插曲
package org.lkg.shiro;
import lombok.Data;
/**
* @description:
* @author:fuchen
* @date: 2021/2/2 12:38
**/
class A{
int id;
}
@Data
class B extends A{
private String name;
private double score;
}
public class Testmain {
public static void main(String[] args) {
B b1 = new B();
b1.id = 1;
b1.setScore(2);
b1.setName("test");
B b2 = new B();
b2. id = 3;
b2.setScore(2);
b2.setName("test");
System.out.println(b1 == b2);
System.out.println(b1.equals(b2));
}
}
執行結果:
false
true
猜想: @Data重寫的equals沒有比較父類的id
於是乎用jad反編譯了B的原始碼
public B()
{
}
public String getName()
{
return name;
}
public double getScore()
{
return score;
}
public void setName(String name)
{
this.name = name;
}
public void setScore(double score)
{
this.score = score;
}
public boolean equals(Object o)
{
//false
if(o == this)
return true;
//true
if(!(o instanceof B))
return false;
B other = (B)o;
//true
if(!other.canEqual(this))
return false;
//true
if(Double.compare(getScore(), other.getScore()) != 0)
return false;
Object this$name = getName();
Object other$name = other.getName();
//true
return this$name != null ? this$name.equals(other$name) : other$name == null;
}
protected boolean canEqual(Object other)
{
return other instanceof B;//true
}
public int hashCode()
{
int PRIME = 59;
int result = 1;
long $score = Double.doubleToLongBits(getScore());
result = result * 59 + (int)($score >>> 32 ^ $score);
Object $name = getName();
result = result * 59 + ($name != null ? $name.hashCode() : 43);
return result;
}
public String toString()
{
return (new StringBuilder()).append("B(name=").append(getName()).append(", score=").append(getScore()).append(")").toString();
}
private String name;
private double score;
}
確實驗證了猜想, 也就是說@Data 預設不考慮繼承自父類的屬性, 於是乎它提供一個
@EqualsAndHashCode
註解裡的callSuper屬性 將其預設值改為true 即可 意思比較時讓父類成員參與進來
這個時候再去反編譯 B.class多了一條
if(!super.equals(o)) return false;
這樣不由得出現一個問題:在繼承體系中, 如果使用@EqualsAndHashCode(callSuper=true),顯然要求父類要麼也用lombok的@Data/@EqualsAndHashCode(父類無需指定)註解
否則在比較的時候由於父類沒有重寫equals方法而導致直接使用Object 的預設實現:
public boolean equals(Object obj) { return (this == obj);}
那麼就算兩個物件屬性值完全一致, 比較結果仍然是false
舉個例子: 對上面的程式碼做個修改 其他不變
class A{
@Getter
@Setter
private int id;
}
@EqualsAndHashCode(callSuper = true)
class B extends A{
//...同上
}
public static void main(String[] args) {
B b1 = new B();
b1.setId(5);
b1.setScore(2);
b1.setName("test");
B b2 = new B();
b2.setId(5);
b2.setScore(2);
b2.setName("test");
//false
System.out.println(b1.equals(b2));
}
所以該怎麼破?
- lombok在繼承體系中慎用! 如果使用lombok, 父子類都需要加入@Data / @EqualsAndHashCode註解
- 如果不用lombok, 一切都好商量什麼: 即用即重寫
@Data
**@Data註解包括@Getter 、@Setter、@ EqualsAndHashCode(callSuper預設false)、@ToString **
再說一個@Data有意思的地方
使用了@Data 或者 @EqualsAndHashCode註解 不允許再重寫equals, 否則直接執行出錯
@EqualsAndHashCode
上面提到不少@EqualsAndHashCode註解, 它很值得研究 現在玩一玩有意思的地方
今天在看lombok原始碼的時 發現當前版本EqualsAndHashCode有一個特性
具體:
生成的equals方法將首先比較原語,然後是原語包裝,然後是引用欄位。可以使用’ @Include(rank=n) '手動重新排序。
來一份註釋大家就明白其中幾個關鍵名字
public boolean equals(Object o)
{
//以下時比較原語 固定rank = 1000
if(o == this)
return true;
if(!(o instanceof B))
return false;
//以下時原語包裝 固定rank = 800
B other = (B)o;
if(!other.canEqual(this))
return false;
if(!super.equals(o))
return false;
//以下都是引用欄位
Object this$name = getName();
Object other$name = other.getName();
if(this$name != null ? !this$name.equals(other$name) : other$name != null)
return false;
return Double.compare(getScore(), other.getScore()) == 0;
}
註釋中 提到一個rank它就代表 這個特性的核心 即可以手動調整equals方法的引用欄位比較順序
老實說, 無法改變比較原語 和原語包裝的順序, 因為從邏輯上二者有因果關係, 所以無法改變; 理論上我們自定義的欄位rank 可以是任意值; 由於因果關系的存在即便設定成>1000的 仍然只對自定義的欄位生效
Tip:
@EqualsAndHashCode.Include(rank = 1750)
private String name;
@EqualsAndHashCode.Include(rank = 1700)
private double score;
反編譯後 的equals 引用欄位的比較順序發生改變
未加入@include
加入後
還有一個@exclude, 顧名思義就是比較時不考慮的欄位咯, 有興趣的話可以自我嘗試.
@Accessors
這個更好玩了(貪玩coding, 你沒有見過的全新碼農), 翻譯就像Shiro的Realm一樣極不友好(預告篇哦), 晦澀難懂
但是一點也不妨礙它的實用性, 有時候物件複製如果引數太多 要麼一直setXx() 要麼加全參構造
Lombok考慮很周到, 採用@Accessors(chain=true) 指定鏈式賦值
//普通
b1.setScore(2);
b1.setName("test");
//使用@Accessors(chian=true)後
b1.setName().setScore()
還有一個屬性 @Accessors(fluent = true) 這個意味著不再按照我們傳統的思路對屬性set/get時 新增對應的set/get字首 直接使用原屬性賦值和取值
效果如下
//對name的get/set 不再有字首 根據傳引數判定是取or放
b1.name("test");
System.out.println(b1.name());
看個人習慣, 但是我不推薦各位使用, 因為我看了牛逼哄哄的阿里開發規範, 上面就提到對is字首的boolean型別進行RPC逆向解析 會出錯
, 如果採用改註解那麼 不就撞槍口上了嗎
End
平時使用lombok沒注意的地方 大概就是這些啦, 後期如果遇到新的 也會持續更新, 不搬無用之磚!