那些年匆匆而过的砖-lombok在继承体系中到底改怎么用EqualsAndHashCode 破晓篇

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 注意我们期望的是b1和b2是相等的
        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没注意的地方 大概就是这些啦, 后期如果遇到新的 也会持续更新, 不搬无用之砖!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值