虽然接触到lombok已经有很长时间,可是大量使用lombok以减小代码编写仍是在新团队编写新代码维护老代码中遇到的。java
我我的并不主张使用lombok,其带来的代价足以抵消其便利,可是因为团队编码风格须要一致,用仍是要继续使用下去。使用期间遇到了一些问题并进行了一番研究和思考,记录一下。eclipse
这些是最初我不喜欢lombok的缘由。ide
做为IDE插件+jar包,须要对IDE进行一系列的配置。目前在idea中配置还算简单,几年前在eclipse下也配置过,会复杂很多。测试
通常来讲,对外打的jar包最好尽量地减小三方包依赖,这样能够加快编译速度,也能减小版本冲突。一旦在resource包里用了lombok,别人想看源码也不得不装插件。this
而这种不在对外jar包中使用lombok仅仅是约定俗成,当某一天lombok第一次被引入这个jar包时,新的感染者没法避免。编码
定位方法调用时,对于自动生成的代码,getter/setter还好说,找到成员变量后find usages,再根据上下文区分是哪一种;equals()这种,想找就只能写段测试代码再去find usages了。idea
目前主流ide基本都支持自动生成getter/setter代码,和lombok注解相比不过一次键入仍是一次快捷键的区别,实际减轻的工做量十分微小。.net
当这个注解设置callSuper=true
时,会调用父类的equlas()方法,对应编译后class文件代码片断以下:插件
public boolean equals(Object o) { if (o == this) { return true; } else if (!(o instanceof BaseVO)) { return false; } else { BaseVO other = (BaseVO)o; if (!other.canEqual(this)) { return false; } else if (!super.equals(o)) { return false; } else { // 各项属性比较 } } }
若是一个类的父类是Object(java中默认没有继承关系的类父类都是Object),那么这里会调用Object的equals()方法,以下code
public boolean equals(Object obj) { return (this == obj); }
对于父类是Object且使用了@EqualsAndHashCode(callSuper = true)
注解的类,这个类由lombok生成的equals()方法只有在两个对象是同一个对象时,才会返回true,不然总为false,不管它们的属性是否相同。这个行为在大部分时间是不符合预期的,equals()失去了其意义。即便咱们指望equals()是这样工做的,那么其他的属性比较代码即是累赘,会大幅度下降代码的分支覆盖率。以一个近6000行代码的业务系统举例,是否修复该问题并编写对应测试用例,可使总体的jacoco分支覆盖率提升10%~15%。
相反地,因为这个注解在jacoco下只算一行代码,未覆盖行数倒不会太多。
有几种解决方法能够参考:
callSuper = true
。若是父类是Object,推荐使用。@data
注解包含@EqualsAndHashCode
注解,因为不调用父类equals(),避免了Object.equals()的坑,但可能带来另外一个坑。详见@data章节
。
上文提到@EqualsAndHashCode(callSuper = true) 注解的坑,那么 @data
是否能够避免呢?很不幸的是,这里也有个坑。
因为 @data
实际上就是用的 @EqualsAndHashCode
,没有调用父类的equals(),当咱们须要比较父类属性时,是没法比较的。示例以下:
@Data public class ABO { private int a; } @Data public class BBO extends ABO { private int b; public static void main(String[] args) { BBO bbo1 = new BBO(); BBO bbo2 = new BBO(); bbo1.setA(1); bbo2.setA(2); bbo1.setB(1); bbo2.setB(1); System.out.print(bbo1.equals(bbo2)); // true } }
很显然,两个子类忽略了父类属性比较。这并非由于父类的属性对于子类是不可见——即便把父类private属性改为protected,结果也是同样——而是由于lombok自动生成的equals()只比较子类特有的属性。
@data
就不要有继承关系,相似kotlin的作法,具体探讨见下一节@EqualsAndHashCode(callSuper = true)
。lombok会以显式指定的为准。在了解了 @data
的行为后,会发现它和kotlin语言中的data修饰符有点像:都会自动生成一些方法,而且在继承上也有问题——前者一旦有继承关系就会踩坑,然后者修饰的类是final的,不容许继承。kotlin为何要这样作,两者有没有什么联系呢?在一篇流传较广的文章(抛弃 Java 改用 Kotlin 的六个月后,我后悔了(译文))中,对于data修饰符,提到:
Kotlin 对 equals()、hashCode()、toString() 以及 copy() 有很好的实现。在实现简单的DTO 时它很是有用。但请记住,数据类带有严重的局限性。你没法扩展数据类或者将其抽象化,因此你可能不会在核心模型中使用它们。
这个限制不是 Kotlin 的错。在 equals() 没有违反 Liskov 原则的状况下,没有办法产生正确的基于值的数据。
对于Liskov(里氏替换)原则,能够简单归纳为:
一个对象在其出现的任何地方,均可以用子类实例作替换,而且不会致使程序的错误。换句话说,当子类能够在任意地方替换基类且软件功能不受影响时,这种继承关系的建模才是合理的。
根据上一章的讨论,equals()的实现其实是受业务场景影响的,不管是否使用父类的属性作比较都是有可能的。可是kotlin没法决定equals()默认的行为,不使用父类属性就会违反了这个原则,使用父类属性有可能落入调用Object.equals()的陷阱,进入了两难的境地。
kotlin的开发者回避了这个问题,不使用父类属性而且禁止继承便可。只是kotlin的使用者就会发现本身定义的data对象无法继承,不得不删掉这个关键字手写其对应的方法。
回过头来再看 @data
,它并无避免这些坑,只是把更多的选择权交给开发者决定,是另外一种作法。
其余lombok注解实际使用较少,总体阅读了 官方文档暂时没有发现其余问题,遇到之后继续更新。 实际上官方文档中也提到了equals()的坑。