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));
}
所以该怎么破?
- 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没注意的地方 大概就是这些啦, 后期如果遇到新的 也会持续更新, 不搬无用之砖!