Java Lombok简介、使用、工作原理、优缺点
简介
官方介绍
ProjectLombokisajavalibrarythatautomaticallyplugsintoyoureditorandbuildtools,spicingupyourjava.Neverwriteanothergetterorequalsmethodagain,withoneannotationyourclasshasafullyfeaturedbuilder,automateyourloggingvariables,andmuchmore.
翻译之后就是:
Lombok项目是一个Java库,它会自动插入您的编辑器和构建工具中,简化您的Java。不需要再写另一个getter、setter、toString或equals方法,带有一个注释的您的类有一个功能全面的生成器,可以自动化您的日志记录变量,以及更多其他功能
官网链接
使用
添加maven依赖
org.projectlombok lombok 1.18.16 provided
注意:在这里scope要设置为provided,防止依赖传递给其他项目
安装插件(可选)
在开发过程中,一般还需要配合插件使用,在IDEA中需要安装Lombok插件即可
为什么要安装插件?
首先在不安装插件的情况下,代码是可以正常的编译和运行的。如果不安装插件,IDEA不会自动提示Lombok在编译时才会生成的一些样板方法,同样IDEA在校验语法正确性的时候也会提示有问题,会有大面积报红的代码
示例
下面举两个栗子,看看使用lombok和不使用的区别
创建一个用户类
不使用Lombok
publicclassUser{ privateIntegerid; privateIntegerage; privateStringrealName; publicUser(){ } @Override publicbooleanequals(Objecto){ if(this==o){ returntrue; } if(o==null||getClass()!=o.getClass()){ returnfalse; } Useruser=(User)o; if(!Objects.equals(id,user.id)){ returnfalse; } if(!Objects.equals(age,user.age)){ returnfalse; } returnObjects.equals(realName,user.realName); } @Override publicinthashCode(){ intresult=id!=null?id.hashCode():0; result=31*result+(age!=null?age.hashCode():0); result=31*result+(realName!=null?realName.hashCode():0); returnresult; } @Override publicStringtoString(){ return"User{"+ "id="+id+ ",age="+age+ ",realName='"+realName+'\''+ '}'; } publicIntegergetId(){ returnid; } publicvoidsetId(Integerid){ this.id=id; } publicIntegergetAge(){ returnage; } publicvoidsetAge(Integerage){ this.age=age; } publicStringgetRealName(){ returnrealName; } publicvoidsetRealName(StringrealName){ this.realName=realName; } }
使用Lombok
@Data publicclassUser{ privateIntegerid; privateStringusername; privateIntegerage; }
使用@Data注解会在编译的时候自动生成以下模板代码:
- toString
- equals
- hashCode
- getter不会对final属性生成
- setter不会对final属性生成
- 必要参数的构造器
关于什么是必要参数下面会举例说明
全部注解
上面已经简单看了一下@Data注解,下面看下所有的可以用的注解
@NonNull注解在字段和构造器的参数上。注解在字段上,则在setter,constructor方法中加入判空,注意这里需要配合@Setter、@RequiredArgsConstructor、@AllArgsConstructor使用;注解在构造器方法参数上,则在构造的时候加入判空
@Cleanup注解在本地变量上。负责清理资源,当方法直接结束时,会调用close方法
@Setter注解在类或字段。注解在类时为所有字段生成setter方法,注解在字段上时只为该字段生成setter方法,同时可以指定生成的setter方法的访问级别
@Getter使用方法同@Setter,区别在于生成的是getter方法
@ToString注解在类上。添加toString方法
@EqualsAndHashCode注解在类。生成hashCode和equals方法
@NoArgsConstructor注解在类。生成无参的构造方法。
@RequiredArgsConstructor注解在类。为类中需要特殊处理的字段生成构造方法,比如final和被@NonNull注解的字段。
@AllArgsConstructor注解在类,生成包含类中所有字段的构造方法。
@Data注解在类,生成setter/getter、equals、canEqual、hashCode、toString方法,如为final属性,则不会为该属性生成setter方法。
@Value注解在类和属性上。如果注解在类上在类实例创建后不可修改,即不会生成setter方法,这个会导致@Setter不起作用
@Builder注解在类上,生成构造器
@SneakyThrows
@Synchronized注解在方法上,生成同步方法
@With
日志相关:注解在类,生成log常量,类似privatestaticfinalxxxlog
- @Logjava.util.logging.Logger
- @CommonsLogorg.apache.commons.logging.Log
- @Floggercom.google.common.flogger.FluentLogger
- @JBossLogorg.jboss.logging.Logger
- @Log4jorg.apache.log4j.Logger
- @Log4j2org.apache.logging.log4j.Logger
- @Slf4jorg.slf4j.Logger
- @XSlf4jorg.slf4j.ext.XLogger
关于所有的注解可以查看https://projectlombok.org/features/all
综合实例
综合实例一
importlombok.AccessLevel; importlombok.AllArgsConstructor; importlombok.Builder; importlombok.EqualsAndHashCode; importlombok.Getter; importlombok.NonNull; importlombok.RequiredArgsConstructor; importlombok.Setter; importlombok.ToString; @Getter//生成getter @AllArgsConstructor//生成所有的参数 @RequiredArgsConstructor//生成必要参数的构造器 @ToString//生成toString @EqualsAndHashCode//生成equals和hashCode @Builder//生成一个builder publicclassUserLombok{ //创建setter并且校验id不能为空 @Setter @NonNull privateIntegerid; //创建setter且生成方法的访问级别为PROTECTED @Setter(AccessLevel.PROTECTED) privateIntegerage; //创建setter不校验是否为空 @Setter privateStringrealName; //构造器,校验id不能为空 publicUserLombok(@NonNullIntegerid,Integerage){ this.id=id; this.age=age; } /** *自定义realName的setter方法,这个优先高于Lombok *@paramrealName真实姓名 */ publicvoidsetRealName(StringrealName){ this.realName="realName:"+realName; } }
具体生成的类为
importlombok.NonNull; publicclassUserLombok{ @NonNull privateIntegerid; privateIntegerage; privateStringrealName; publicUserLombok(@NonNullIntegerid,Integerage){ if(id==null){ thrownewNullPointerException("idismarkednon-nullbutisnull"); }else{ this.id=id; this.age=age; } } publicvoidsetRealName(StringrealName){ this.realName="realName:"+realName; } publicstaticUserLombok.UserLombokBuilderbuilder(){ returnnewUserLombok.UserLombokBuilder(); } @NonNull publicIntegergetId(){ returnthis.id; } publicIntegergetAge(){ returnthis.age; } publicStringgetRealName(){ returnthis.realName; } publicUserLombok(@NonNullIntegerid,Integerage,StringrealName){ if(id==null){ thrownewNullPointerException("idismarkednon-nullbutisnull"); }else{ this.id=id; this.age=age; this.realName=realName; } } publicUserLombok(@NonNullIntegerid){ if(id==null){ thrownewNullPointerException("idismarkednon-nullbutisnull"); }else{ this.id=id; } } publicStringtoString(){ return"UserLombok(id="+this.getId()+",age="+this.getAge()+",realName="+this.getRealName()+")"; } publicbooleanequals(Objecto){ if(o==this){ returntrue; }elseif(!(oinstanceofUserLombok)){ returnfalse; }else{ UserLombokother=(UserLombok)o; if(!other.canEqual(this)){ returnfalse; }else{ label47:{ Objectthis$id=this.getId(); Objectother$id=other.getId(); if(this$id==null){ if(other$id==null){ breaklabel47; } }elseif(this$id.equals(other$id)){ breaklabel47; } returnfalse; } Objectthis$age=this.getAge(); Objectother$age=other.getAge(); if(this$age==null){ if(other$age!=null){ returnfalse; } }elseif(!this$age.equals(other$age)){ returnfalse; } Objectthis$realName=this.getRealName(); Objectother$realName=other.getRealName(); if(this$realName==null){ if(other$realName!=null){ returnfalse; } }elseif(!this$realName.equals(other$realName)){ returnfalse; } returntrue; } } } protectedbooleancanEqual(Objectother){ returnotherinstanceofUserLombok; } publicinthashCode(){ intPRIME=true; intresult=1; Object$id=this.getId(); intresult=result*59+($id==null?43:$id.hashCode()); Object$age=this.getAge(); result=result*59+($age==null?43:$age.hashCode()); Object$realName=this.getRealName(); result=result*59+($realName==null?43:$realName.hashCode()); returnresult; } publicvoidsetId(@NonNullIntegerid){ if(id==null){ thrownewNullPointerException("idismarkednon-nullbutisnull"); }else{ this.id=id; } } protectedvoidsetAge(Integerage){ this.age=age; } publicstaticclassUserLombokBuilder{ privateIntegerid; privateIntegerage; privateStringrealName; UserLombokBuilder(){ } publicUserLombok.UserLombokBuilderid(@NonNullIntegerid){ if(id==null){ thrownewNullPointerException("idismarkednon-nullbutisnull"); }else{ this.id=id; returnthis; } } publicUserLombok.UserLombokBuilderage(Integerage){ this.age=age; returnthis; } publicUserLombok.UserLombokBuilderrealName(StringrealName){ this.realName=realName; returnthis; } publicUserLombokbuild(){ returnnewUserLombok(this.id,this.age,this.realName); } publicStringtoString(){ return"UserLombok.UserLombokBuilder(id="+this.id+",age="+this.age+",realName="+this.realName+")"; } } }
综合实例二
@Value publicclassUserLombok{ @NonNull privateIntegerid; //这里的setter不会生成,所有没用,这里反面示例 @Setter(AccessLevel.PROTECTED) privateIntegerage; privateStringrealName; }
@Value是ToString、EqualsAndHashCode、AllArgsConstructor、Getter的组合注解
生成的代码
importlombok.NonNull; publicfinalclassUserLombok{ @NonNull privatefinalIntegerid; privatefinalIntegerage; privatefinalStringrealName; publicUserLombok(@NonNullIntegerid,Integerage,StringrealName){ if(id==null){ thrownewNullPointerException("idismarkednon-nullbutisnull"); }else{ this.id=id; this.age=age; this.realName=realName; } } @NonNull publicIntegergetId(){ returnthis.id; } publicIntegergetAge(){ returnthis.age; } publicStringgetRealName(){ returnthis.realName; } publicbooleanequals(Objecto){ if(o==this){ returntrue; }elseif(!(oinstanceofUserLombok)){ returnfalse; }else{ UserLombokother; label44:{ other=(UserLombok)o; Objectthis$id=this.getId(); Objectother$id=other.getId(); if(this$id==null){ if(other$id==null){ breaklabel44; } }elseif(this$id.equals(other$id)){ breaklabel44; } returnfalse; } Objectthis$age=this.getAge(); Objectother$age=other.getAge(); if(this$age==null){ if(other$age!=null){ returnfalse; } }elseif(!this$age.equals(other$age)){ returnfalse; } Objectthis$realName=this.getRealName(); Objectother$realName=other.getRealName(); if(this$realName==null){ if(other$realName!=null){ returnfalse; } }elseif(!this$realName.equals(other$realName)){ returnfalse; } returntrue; } } publicinthashCode(){ intPRIME=true; intresult=1; Object$id=this.getId(); intresult=result*59+($id==null?43:$id.hashCode()); Object$age=this.getAge(); result=result*59+($age==null?43:$age.hashCode()); Object$realName=this.getRealName(); result=result*59+($realName==null?43:$realName.hashCode()); returnresult; } publicStringtoString(){ return"UserLombok(id="+this.getId()+",age="+this.getAge()+",realName="+this.getRealName()+")"; } }
综合实例三
日志使用
importlombok.extern.java.Log; @Log publicclassLogLombok{ publicvoidlog(){ log.info("打个日志"); } }
生成后代码
importjava.util.logging.Logger; publicclassLogLombok{ privatestaticfinalLoggerlog=Logger.getLogger(LogLombok.class.getName()); publicLogLombok(){ } publicvoidlog(){ log.info("打个日志"); } }
通过上面的示例,我们可以看出Lombok可以大大简化我们的代码
Lombok的优缺点
- 优点:
提高开发效率,自动生成getter/setter、toString、builder等,尤其是类不断改变过程中,如果使用IDEA自动生成的代码,我们则需要不停的删除、重新生成,使用Lombok则自动帮助我们完成
让代码变得简洁,不用过多的去关注相应的模板方法,其中getter/setter、toString、builder均为模板代码,写着难受,不写还不行,而且在java14已经开始计划支持record,也在帮我们从原生方面解决这种模板代码
属性做修改时,也简化了维护为这些属性所生成的getter/setter方法等
- 缺点:
不同开发人员同时开发同一个使用Lombok项目、需要安装Lombok插件
不利于重构属性名称,对应的setter、getter、builder,IDEA无法帮助自动重构
有可能降低了源代码的可读性和完整性,降低了阅读源代码的舒适度,谁会去阅读模板代码呢
解决编译时出错问题
编译时出错,可能是没有启用注解处理器。Build,Execution,Deployment>AnnotationProcessors>Enableannotationprocessing。设置完成之后程序正常运行。
避坑指南
- 尽量不要使用@Data注解,这个注解太全了,不利于维护,除非你知道你在干什么
- Java默认机制如果有其他构造器,则不会生成无参构造器,在使用@AllArgsConstructor注解时,记得加上@NoArgsConstructor
- 如果类定义还在变化阶段,不建议使用@AllArgsConstructor注解
- @Setter、@Getter注解如果需要可以缩小使用范围
- @ToString注解默认不会生成父类的信息,如果需要生成需要@ToString(callSuper=true)
- @RequiredArgsConstructor和@NoArgsConstructor尽量不要一起使用,无参构造器无法处理@NonNull,但在序列化/反序列化的还是需要提供无参的
- 当团队决定不再使用Lombok的时候,可以使用Lombok插件的Delombok一键去除,在Refactor>Delombok中
再次注意-@AllArgsConstructor尽量不要使用
参考
https://projectlombok.org
https://github.com/rzwitserloot/lombok
Lombok工作原理
工作原理来自网上资料
在Lombok使用的过程中,只需要添加相应的注解,无需再为此写任何代码。自动生成的代码到底是如何产生的呢?
核心之处就是对于注解的解析上。JDK5引入了注解的同时,也提供了两种解析方式。
- 运行时解析
运行时能够解析的注解,必须将@Retention设置为RUNTIME,这样就可以通过反射拿到该注解。java.lang.reflect反射包中提供了一个接口AnnotatedElement,该接口定义了获取注解信息的几个方法,Class、Constructor、Field、Method、Package等都实现了该接口,对反射熟悉的朋友应该都会很熟悉这种解析方式。
- 编译时解析
编译时解析有两种机制,分别简单描述下:
1)AnnotationProcessingTool
apt自JDK5产生,JDK7已标记为过期,不推荐使用,JDK8中已彻底删除,自JDK6开始,可以使用PluggableAnnotationProcessingAPI来替换它,apt被替换主要有2点原因:
- api都在com.sun.mirror非标准包下
- 没有集成到javac中,需要额外运行
2)PluggableAnnotationProcessingAPI
JSR269自JDK6加入,作为apt的替代方案,它解决了apt的两个问题,javac在执行的时候会调用实现了该API的程序,这样我们就可以对编译器做一些增强,javac执行的过程如下:
Lombok本质上就是一个实现了“JSR269API”的程序。在使用javac的过程中,它产生作用的具体流程如下:
- javac对源代码进行分析,生成了一棵抽象语法树(AST)
- 运行过程中调用实现了“JSR269API”的Lombok程序
- 此时Lombok就对第一步骤得到的AST进行处理,找到@Data注解所在类对应的语法树(AST),然后修改该语法树(AST),增加getter和setter方法定义的相应树节点
- javac使用修改后的抽象语法树(AST)生成字节码文件,即给class增加新的节点(代码块)
通过读Lombok源码,发现对应注解的实现都在HandleXXX中,比如@Getter注解的实现在HandleGetter.handle()。还有一些其它类库使用这种方式实现,比如GoogleAuto、Dagger等等。
以上就是JavaLombok简介、使用、工作原理、优缺点的详细内容,更多关于JavaLombok的资料请关注毛票票其它相关文章!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。