lombok踩坑与思考

From: https://www.cnblogs.com/wuyuegb2312/p/9750462.html

虽然接触到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()的坑。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/402691.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

scheme 学习:红黑树

这几天继续学习scheme,scheme中虽然有hashtable但没有类似C中的map,于是把C版本中的红黑树移植到scheme(中间也发现了C版本中的一些问题,暂时懒得调整了^()^) 以作为后序set和表格驱动设计中表格的基础数据结构. 虽说这个红黑树在C版本中是调试好的了&am…

Maven多模块,Dubbo分布式服务框架,SpringMVC,前后端分离项目,基础搭建,搭建过程出现的问题...

现互联网公司后端架构常用到SpringSpringMVCMyBatis,通过Maven来构建。通过学习,我已经掌握了基本的搭建过程,写下基础文章为而后的深入学习奠定基础。 首先说一下这篇文章的主要内容分为: 1、Maven多模块项目的创建; …

Maven的pom.xml文件结构之Build配置build

From: https://blog.csdn.net/taiyangdao/article/details/52374125 在Maven的pom.xml文件中&#xff0c;Build相关配置包含两个部分&#xff0c;一个是<build>&#xff0c;另一个是<reporting>&#xff0c;这里我们只介绍<build>。 1. 在Maven的pom.xml文件…

FineUI 将不再内置 ExtJS (严格遵守 ExtJS 的开源规则)

从下个版本起&#xff0c;FineUI 将不再内置 ExtJS &#xff0c;不过我会提供说明如何使用 ExtJS&#xff08;并单独提供精简版的 ExtJS 包供论坛用户下载&#xff09;&#xff0c;现有的程序升级到新版本将不受影响&#xff08;只需要将精简版的 extjs 文件夹拷贝到程序根目录…

Maven开发笔记(四)—— Maven中plugins和pluginManagement

From: https://www.jianshu.com/p/49acf1246eff 1.plugins和pluginManagement的区别概述 plugins 和 pluginManagement 的区别&#xff0c;和我们前面研究过的 dependencies 和 dependencyManagement 的区别是非常类似的。plugins 下的 plugin 是真实使用的&#xff0c;而 pl…

Html代码seo优化最佳布局实例讲解

搜索引擎对html代码是非常优化的&#xff0c;所以html的优化是做好推广的第一步。一个符合seo规则的代码大体如下界面所示。 1、<!–木庄网络博客–> 这个东西是些页面注释的&#xff0c;可以在这里加我的“木庄网络博客”&#xff0c;但过多关键字可能被搜索引擎惩罚&a…

基于struts2,hibernate的小javaweb项目

19:47:49 这是截图 闲话不说 就开始了 web-xml&#xff1a; <?xml version"1.0" encoding"UTF-8"?> <web-app version"2.5" xmlns"http://java.sun.com/xml/ns/javaee"xmlns:xsi"http://www.w3.org/2001/XMLSchema-in…

YAML快速入门

From: https://www.jianshu.com/p/97222440cd08 我们学习Java&#xff0c;都是先介绍properties文件&#xff0c;使用properties文件配合Properties对象能够很方便的适用于应用配置上。然后在引入XML的时候&#xff0c;我们介绍properties格式在表现层级关系和结构关系的时候&…

MySql的用户权限

用户管理 MySQL数据库中的表与其他任何关系表没有区别&#xff0c;都可以通过典型的SQL命令修改其结构和数据。可以使用GRANT和REVOKE命令。通过这些命令&#xff0c;可以创建和禁用用户&#xff0c;可以在线授予和撤回用户访问权限。在5.0版本中增加了两个新命令&#xff1a;C…

.yaml 文件格式简介

From: https://www.cnblogs.com/wxmdevelop/p/7341292.html YAML 的意思其实是&#xff1a;"Yet Another Markup Language"&#xff08;仍是一种置标语言&#xff09;的缩写。 功能 YAML的语法和其他高阶语言类似&#xff0c;并且可以简单表达清单、散列表&#x…

jQuery与JS的区别,以及jQuery的基础语法

*在使用jQuery时&#xff0c;要在页面最上端加上 <script src"../jquery-1.11.2.min.js"></script> 看一下js与jQuery的区别&#xff1a; JS是这样使用的&#xff1a; <script type"text/javascript">根据ID取元素,取到的是具体的元素va…

sql语句中的时间查询

一般来说&#xff0c;我们在mysql数据库纪录数据时间时&#xff0c;都会选择datatime类型&#xff0c;这样时间可以精确到秒。但随之而来的一个问题是&#xff0c;当我们要取得某一段时间内的数据内容会有一些时间转换上的麻烦&#xff0c;例如我们要取得2002年3月2日到2003年7…

SnakeYaml快速入门

From: https://www.jianshu.com/p/d8136c913e52 在YAML快速入门[https://www.jianshu.com/p/97222440cd08]中&#xff0c;我们已经简单介绍了YAML的语法&#xff0c;本节中主要介绍YAML的配置读取。 目前有很多可以生成和解析YAML的第三方工具&#xff0c;常见的&#xff0c;…

CSS的一些零碎总结

1、CSS 伪元素用于向某些选择器设置特殊效果&#xff08;用来当作一个东西的&#xff0c;跟一个元素差不多&#xff0c;但不是元素&#xff09;。 ① :frist-line伪元素&#xff1a;用于向文本首行设置特殊样式&#xff0c;但是只能用于块级元素。 以下属性可应用于 “ frist-l…

有源代码的iphone项目

2019独角兽企业重金招聘Python工程师标准>>> http://blog.joomla.org.tw/iphone-ipad/104-iphone.html 學習和利用現成的資源是很重要的&#xff0c;以下列出有原始碼可下載的iPhone/iPod程式&#xff0c;這邊收集的是以已經放到App Store上的程式為主&#xff0c;…

匿名函数与闭包

<!DOCTYPE html><html lang"zh-CN"><head> <meta charset"UTF-8"> <title>闭包.html</title> <style> </style> <script src"jquery-1.7.2.min.js"></script> <script type&quo…

Content Security Policy 入门教程

From: http://www.ruanyifeng.com/blog/2016/09/csp.html 跨域脚本攻击 XSS 是最常见、危害最大的网页安全漏洞。 为了防止它们&#xff0c;要采取很多编程措施&#xff0c;非常麻烦。很多人提出&#xff0c;能不能根本上解决问题&#xff0c;浏览器自动禁止外部注入恶意脚本&…

windows编程基础

说明&#xff1a;只供学习交流&#xff0c;转载请注明出处 windows编程基础 &#xff08;1&#xff09;&#xff1a;API与SDK 我们在编写标准C程序的时候&#xff0c;经常会调用各种库函数来辅助完成某些功能&#xff1a;初学者使用得最多的库函数就是printf了&#xff0c;这些…

前端安全配置之Content-Security-Policy(csp)

From: https://www.cnblogs.com/heyuqing/p/6215761.html 什么是CSP CSP全称Content Security Policy ,可以直接翻译为内容安全策略,说白了,就是为了页面内容安全而制定的一系列防护策略. 通过CSP所约束的的规责指定可信的内容来源&#xff08;这里的内容可以指脚本、图片、i…

springboot跨域配置

From: https://www.cnblogs.com/nananana/p/8492185.html 前言&#xff1a; 当它请求的一个资源是从一个与它本身提供的第一个资源的不同的域名时&#xff0c;一个资源会发起一个跨域HTTP请求(Cross-site HTTP request)。 比如说&#xff0c;域名A ( http://domaina.example …