1. 程式人生 > 其它 >那些年匆匆而過的磚-lombok 破曉篇

那些年匆匆而過的磚-lombok 破曉篇

技術標籤:java反編譯javalombok

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

    }

所以該怎麼破?

  1. lombok在繼承體系中慎用! 如果使用lombok, 父子類都需要加入@Data / @EqualsAndHashCode註解
  2. 如果不用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沒注意的地方 大概就是這些啦, 後期如果遇到新的 也會持續更新, 不搬無用之磚!