使用Lombok时遇到哪些常见问题,如何避免踩坑?

摘要:lombok使用时遇到的问题以及思考总结,避免后续踩坑。作为话题延伸,探讨了@data和kotlin中的data区别与联系。
虽然接触到lombok已经有很长时间,但是大量使用lombok以减少代码编写还是在新团队编写新代码维护老代码中遇到的。 我个人并不主张使用lombok,其带来的代价足以抵消其便利,但是由于团队编码风格需要一致,用还是要继续使用下去。使用期间遇到了一些问题并进行了一番研究和思考,记录一下。 1. 一些杂七杂八的问题 这些是最初我不喜欢lombok的原因。 1.1 额外的环境配置 作为IDE插件+jar包,需要对IDE进行一系列的配置。目前在idea中配置还算简单,几年前在eclipse下也配置过,会复杂不少。 1.2 传染性 一般来说,对外打的jar包最好尽可能地减少三方包依赖,这样可以加快编译速度,也能减少版本冲突。一旦在resource包里用了lombok,别人想看源码也不得不装插件。 而这种不在对外jar包中使用lombok仅仅是约定俗成,当某一天lombok第一次被引入这个jar包时,新的感染者无法避免。 1.3 降低代码可读性 定位方法调用时,对于自动生成的代码,getter/setter还好说,找到成员变量后find usages,再根据上下文区分是哪种;equals()这种,想找就只能写段测试代码再去find usages了。 目前主流ide基本都支持自动生成getter/setter代码,和lombok注解相比不过一次键入还是一次快捷键的区别,实际减轻的工作量十分微小。 2. @EqualsAndHashCode和equals() 2.1 原理 当这个注解设置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()方法,如下 public boolean equals(Object obj) { return (this == obj); } 2.2 问题 对于父类是Object且使用了@EqualsAndHashCode(callSuper = true) 注解的类,这个类由lombok生成的equals()方法只有在两个对象是同一个对象时,才会返回true,否则总为false,无论它们的属性是否相同。这个行为在大部分时间是不符合预期的,equals()失去了其意义。即使我们期望equals()是这样工作的,那么其余的属性比较代码便是累赘,会大幅度降低代码的分支覆盖率。以一个近6000行代码的业务系统举例,是否修复该问题并编写对应测试用例,可以使整体的jacoco分支覆盖率提高10%~15%。 相反地,由于这个注解在jacoco下只算一行代码,未覆盖行数倒不会太多。 2.3 解决 有几种解决方法可以参考: 不使用该注解。大部分pojo我们是不会调用equals进行比较的,实际用到时再重写即可。 去掉callSuper = true。如果父类是Object,推荐使用。 重写父类的equals()方法,确保父类不会调用或使用类似实现的Ojbect的equals()。 2.4 其他 @data注解包含@EqualsAndHashCode注解,由于不调用父类equals(),避免了Object.equals()的坑,但可能带来另一个坑。详见@data章节。 3. @data 3.1 从一个坑出来掉到另一个大坑 上文提到@EqualsAndHashCode(callSuper = true) 注解的坑,那么 @data 是否可以避免呢?很不幸的是,这里也有个坑。 由于 @data 实际上就是用的 @EqualsAndHashCode,没有调用父类的equals(),当我们需要比较父类属性时,是无法比较的。
阅读全文