• lombok踩坑与思考


    虽然接触到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(),当我们需要比较父类属性时,是无法比较的。示例如下:

    
    @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()只比较子类特有的属性。

    3.2 解决方法

    • 用了 @data 就不要有继承关系,类似kotlin的做法,具体探讨见下一节
    • 自己重写equals(),lombok不会对显式重写的方法进行生成
    • 显式使用@EqualsAndHashCode(callSuper = true)。lombok会以显式指定的为准。

    3.3 关于@data和data

    在了解了 @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 ,它并没有避免这些坑,只是把更多的选择权交给开发者决定,是另一种做法。

    4. 后记

    其他lombok注解实际使用较少,整体阅读了 官方文档暂时没有发现其他问题,遇到以后继续更新。
    实际上官方文档中也提到了equals()的坑。

  • 相关阅读:
    Fibonacci数列的递推公式为:Fn=Fn-1+Fn-2,其中F1=F2=1。
    ps中的中英文对照
    2019.6.27 oracle复习 表空间
    pthon学习笔记 2020/4/6
    运维岗位发展方向
    sql server复习重点
    linux的shell script
    linux知识扫盲
    Android Studio 三、软件学习教程-知识点
    Android Studio 二、github项目下载 2019.8.23
  • 原文地址:https://www.cnblogs.com/wuyuegb2312/p/9750462.html
Copyright © 2020-2023  润新知