Lombok是一款Java开发插件,使得Java开发者能够经过其定义的一些注解来消除业务工程中冗长和繁琐的代码,尤为对于简单的Java模型对象(POJO)。在开发环境中使用Lombok插件后,Java开发人员能够节省出重复构建,诸如hashCode和equals这样的方法以及各类业务对象模型的accessor和ToString等方法的大量时间。对于这些方法,它可以在编译源代码期间自动帮咱们生成这些方法,并无如反射那样下降程序的性能。 java
在项目中使用Lombok能够减小不少重复代码的书写。好比说getter/setter/toString等方法的编写。express
IDEA中的安装
打开IDEA的Setting –> 选择Plugins选项 –> 选择Browse repositories –> 搜索lombok –> 点击安装 –> 安装完成重启IDEA –> 安装成功apache
引入依赖
当前使用版本为2018年最新版本:app
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <scope>provided</scope> <version>1.18.2-RELEASE</version> </dependency>
注解列表ide
先介绍这一波最经常使用的注解:函数
@NoArgsConstructor/@RequiredArgsConstructor /@AllArgsConstructor
这三个注解都是用在类上的,第一个和第三个都很好理解,就是为该类产生无参的构造方法和包含全部参数的构造方法,第二个注解则使用类中全部带有@NonNull注解的或者带有final修饰的成员变量生成对应的构造方法,固然,和前面几个注解同样,成员变量都是非静态的。另外,若是类中含有final修饰的成员变量,是没法使用@NoArgsConstructor注解的。
三个注解均可以指定生成的构造方法的访问权限,还可指定生成一个静态方法使用案例:工具
@AllArgsConstructor public class Demo { private String name; private int age; } @AllArgsConstructor class Parent { private Integer id; }
编译后的两个class文件以下:性能
public class Demo { private String name; private int age; public Demo(String name, int age) { this.name = name; this.age = age; } } //第二个类 class Parent { private Integer id; public Parent(Integer id) { this.id = id; } }
由此课件,此注解并不会把父类的属性id拿到Demo的构造器里面去,这是须要注意的地方。而且它也没有默认的构造器了开发工具
@AllArgsConstructor(access = AccessLevel.PROTECTED, staticName = "test") public class Demo { private final int finalVal = 10; private String name; private int age; }
生成以下:ui
public class Demo { private final int finalVal = 10; private String name; private int age; private Demo(String name, int age) { this.name = name; this.age = age; } protected static Demo test(String name, int age) { return new Demo(name, age); } }
看出来的效果为:能够指定生成的构造器的访问权限。可是,可是若是指定了一个静态方法,那么构造器会自动会被private,只经过静态方法对外提供反问,而且咱们发现final的属性值,是不会放进构造函数里面的。
NoArgsConstructor的使用方式同上,RequiredArgsConstructor看看效果:
@RequiredArgsConstructor public class Demo { private final int finalVal = 10; @NonNull private String name; @NonNull private int age; }
编译后:
public class Demo { private final int finalVal = 10; @NonNull private String name; @NonNull private int age; public Demo(@NonNull String name, @NonNull int age) { if (name == null) { throw new NullPointerException("name is marked @NonNull but is null"); } else { this.name = name; this.age = age; } } }
解释:该注解会识别@nonNull字段,而后以该字段为元素产生一个构造函数。备注:若是全部字段都没有@nonNull注解,那效果同NoArgsConstructor
@Builder 提供了一种比较推崇的构建值对象的方式
很是推荐的一种构建值对象的方式。缺点就是父类的属性不能产于builder
@Builder public class Demo { private final int finalVal = 10; private String name; private int age; }
编译后:
public class Demo { private final int finalVal = 10; private String name; private int age; Demo(String name, int age) { this.name = name; this.age = age; } public static Demo.DemoBuilder builder() { return new Demo.DemoBuilder(); } public static class DemoBuilder { private String name; private int age; DemoBuilder() { } public Demo.DemoBuilder name(String name) { this.name = name; return this; } public Demo.DemoBuilder age(int age) { this.age = age; return this; } public Demo build() { return new Demo(this.name, this.age); } public String toString() { String var10000 = this.name; return this.age; } } }
所以咱们构造一个对象就能够优雅的这么来:
public static void main(String[] args) { Demo demo = Demo.builder().name("aa").age(10).build(); System.out.println(demo); }
里面有一些自定义参数,我表示,彻底没有必要去自定义。
@Cleanup 可以自动释放资源
这个注解用在变量前面,能够保证此变量表明的资源会被自动关闭,默认是调用资源的close()方法。若是该资源有其它关闭方法,可以使用@Cleanup(“methodName”)来指定要调用的方法,就用输入输出流来举个例子吧:
public static void main(String[] args) throws Exception { @Cleanup InputStream in = new FileInputStream(args[0]); @Cleanup OutputStream out = new FileOutputStream(args[1]); byte[] b = new byte[1024]; while (true) { int r = in.read(b); if (r == -1) break; out.write(b, 0, r); } }
编译后:
public static void main(String[] args) throws Exception { FileInputStream in = new FileInputStream(args[0]); try { FileOutputStream out = new FileOutputStream(args[1]); try { byte[] b = new byte[1024]; while(true) { int r = in.read(b); if (r == -1) { return; } out.write(b, 0, r); } } finally { if (Collections.singletonList(out).get(0) != null) { out.close(); } } } finally { if (Collections.singletonList(in).get(0) != null) { in.close(); } } }
就这么简单的一个注解,就实现了优雅的关流操做哟。
@Data 强悍的组合功能包
至关于注解集合。效果等同于**@Getter + @Setter + @ToString + @EqualsAndHashCode + @RequiredArgsConstructor** 因为生成的代码篇幅太长,这里就不给demo了,反正效果同上5个注解的效果,强悍
须要注意的是,这里不包括@NoArgsConstructor和@AllArgsConstructor
@Value注解和@Data相似,区别在于它会把全部成员变量默认定义为private final修饰,而且不会生成set方法。
因此@Value更适合只读性更强的类,因此特殊状况下,仍是可使用的。
@ToString/@EqualsAndHashCode
这两个注解也比较好理解,就是生成toString,equals和hashcode方法,同时后者还会生成一个canEqual方法,用于判断某个对象是不是当前类的实例。,生成方法时只会使用类中的非静态成员变量,这些都比较好理解。毕竟静态的东西并不属于对象自己
@ToString public class Demo { private final int finalVal = 10; private transient String name = "aa"; private int age; } public static void main(String[] args) throws Exception { Demo demo = new Demo(); System.out.println(demo); //Demo(finalVal=10, age=0) }
咱们发现静态字段它是不输出的。
有些关键的属性,能够控制toString的输出,咱们能够了解一下:
@ToString( includeFieldNames = true, //是否使用字段名 exclude = {"name"}, //排除某些字段 of = {"age"}, //只使用某些字段 callSuper = true //是否让父类字段也参与 默认false )
备注:大多数状况下,使用默认的便可,毕竟大多数状况都是POJO
@Generated:暂时貌似没什么用
@Getter/@Setter
这一对注解从名字上就很好理解,用在成员变量上面或者类上面,至关于为成员变量生成对应的get和set方法,同时还能够为生成的方法指定访问修饰符,固然,默认为public
这两个注解直接用在类上,能够为此类里的全部非静态成员变量生成对应的get和set方法。若是是final变量,那就只会有get方法
@Getter @Setter public class Demo { private final int finalVal = 10; private String name; private int age; }
编译后:
public class Demo { private final int finalVal = 10; private String name; private int age; public Demo() { } public int getFinalVal() { Objects.requireNonNull(this); return 10; } public String getName() { return this.name; } public int getAge() { return this.age; } public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; } }
@NonNull
这个注解能够用在成员方法或者构造方法的参数前面,会自动产生一个关于此参数的非空检查,若是参数为空,则抛出一个空指针异常。
//成员方法参数加上@NonNull注解 public String getName(@NonNull Person p){ return p.getName(); }
编译后:
public String getName(@NonNull Person p){ if(p==null){ throw new NullPointerException("person"); } return p.getName(); }
@Singular 默认值 暂时也没太大用处
@SneakyThrows
这个注解用在方法上,能够将方法中的代码用try-catch语句包裹起来,捕获异常并在catch中用Lombok.sneakyThrow(e)把异常抛出,可使用@SneakyThrows(Exception.class)的形式指定抛出哪一种异常
@SneakyThrows(UnsupportedEncodingException.class) public String utf8ToString(byte[] bytes) { return new String(bytes, "UTF-8"); }
编译后:
@SneakyThrows(UnsupportedEncodingException.class) public String utf8ToString(byte[] bytes) { try{ return new String(bytes, "UTF-8"); }catch(UnsupportedEncodingException uee){ throw Lombok.sneakyThrow(uee); } }
这里有必要贴出来Lombok.sneakyThrow的代码:
public static RuntimeException sneakyThrow(Throwable t) { if (t == null) { throw new NullPointerException("t"); } else { return (RuntimeException)sneakyThrow0(t); } } private static <T extends Throwable> T sneakyThrow0(Throwable t) throws T { throw t; }
其实就是转化为了RuntimeException,其实我想说,这个注解也没大用。毕竟咱们碰到异常,是但愿本身处理的
@Synchronized
这个注解用在类方法或者实例方法上,效果和synchronized关键字相同,区别在于锁对象不一样,对于类方法和实例方法,synchronized关键字的锁对象分别是类的class对象和this对象,而@Synchronized得锁对象分别是私有静态final对象LOCK和私有final对象lock,固然,也能够本身指定锁对象
@Synchronized public static void hello() { System.out.println("world"); } @Synchronized public int answerToLife() { return 42; } @Synchronized("readLock") public void foo() { System.out.println("bar"); }
编译后:
public static void hello() { Object var0 = $LOCK; synchronized($LOCK) { System.out.println("world"); } } public int answerToLife() { Object var1 = this.$lock; synchronized(this.$lock) { return 42; } } public void foo() { Object var1 = this.readLock; synchronized(this.readLock) { System.out.println("bar"); } }
我只能说,这个注解也挺鸡肋的。@Val 很强的类型推断 var注解,在Java10以后就不能使用了
class Parent { //private static final val set = new HashSet<String>(); //编译不经过 public static void main(String[] args) { val set = new HashSet<String>(); set.add("aa"); System.out.println(set); //[aa] } }
编译后:
class Parent { Parent() { } public static void main(String[] args) { HashSet<String> set = new HashSet(); set.add("aa"); System.out.println(set); } }
这个和Java10里的Var很像,强大的类型推断。而且不能使用在全局变量上,只能使用在局部变量的定义中。
@Log、CommonsLog、Slf4j、XSlf4j、Log4j、Log4j2等日志注解
这个注解用在类上,能够省去从日志工厂生成日志对象这一步,直接进行日志记录,具体注解根据日志工具的不一样而不一样,同时,能够在注解中使用topic来指定生成log对象时的类名。不一样的日志注解总结以下(上面是注解,下面是实际做用):
@CommonsLog private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(LogExample.class); @JBossLog private static final org.jboss.logging.Logger log = org.jboss.logging.Logger.getLogger(LogExample.class); @Log private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(LogExample.class.getName()); @Log4j private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(LogExample.class); @Log4j2 private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(LogExample.class); @Slf4j private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogExample.class); @XSlf4j private static final org.slf4j.ext.XLogger log = org.slf4j.ext.XLoggerFactory.getXLogger(LogExample.class);
这个注解仍是很是有用的,特别是Slf4j这个,在平时开发中挺有用的
@Slf4j class Parent { }
编译后:
class Parent { private static final Logger log = LoggerFactory.getLogger(Parent.class); Parent() { } }
也可topic的名称:
@Slf4j @CommonsLog(topic = "commonLog") class Parent { }
编译后:
class Parent { private static final Logger log = LoggerFactory.getLogger("commonLog"); Parent() { } }
lombok中有experimental的包:
实验性由于:
咱们可能想将这些特性和更彻底的性质支持概念融为一体(普通话:这些性能还在研究)
新特性-须要社区反馈
@Accessors 一个为getter和setter设计的更流畅的注解
这个注解要搭配@Getter与@Setter使用,用来修改默认的setter与getter方法的形式。因此单独使用是没有意义的
@Accessors(fluent = true) @Getter @Setter public class Demo extends Parent { private final int finalVal = 10; private String name; private int age; }
编译后:
public class Demo extends Parent { private final int finalVal = 10; private String name; private int age; public Demo() { } public int finalVal() { Objects.requireNonNull(this); return 10; } public String name() { return this.name; } public int age() { return this.age; } public Demo name(String name) { this.name = name; return this; } public Demo age(int age) { this.age = age; return this; } }
它的三个参数解释:chain 链式的形式 这个特别好用,方法连缀愈来愈方便了
fluent 流式的形式(若无显示指定chain的值,也会把chain设置为true)
prefix 生成指定前缀的属性的getter与setter方法,而且生成的getter与setter方法时会去除前缀
@Accessors(prefix = "xxx") @Getter @Setter public class Demo extends Parent { private final int finalVal = 10; private String xxxName; private int age; }
编译后:
public class Demo extends Parent { private final int finalVal = 10; private String xxxName; private int age; public Demo() { } public String getName() { return this.xxxName; } public void setName(String xxxName) { this.xxxName = xxxName; } }
咱们发现prefix能够在生成get/set的时候,去掉xxx等prefix前缀,达到很好的一致性。可是,可是须要注意,由于此处age没有匹配上xxx前缀,全部根本就不给生成,因此使用的时候必定要注意。
属性名没有一个以其中的一个前缀开头,则属性会被lombok彻底忽略掉,而且会产生一个警告。
@Delegate 注释的属性,会把这个属性对象的公有非静态方法合到当前类
代理模式,把字段的方法代理给类,默认代理全部方法。注意:公共 非静态方法
public class Demo extends Parent { private final int finalVal = 10; @Delegate private String xxxName; private int age; }
编译后:把String类的公共 非静态方法全拿来了 我的以为很鸡肋有木有
public class Demo extends Parent { private final int finalVal = 10; private String xxxName; private int age; public Demo() { } public int length() { return this.xxxName.length(); } public boolean isEmpty() { return this.xxxName.isEmpty(); } public char charAt(int index) { return this.xxxName.charAt(index); } public int codePointAt(int index) { return this.xxxName.codePointAt(index); }
备注:它不能用于基本数据类型字段好比int,只能用在包装类型好比Integer
参数们:
types:指定代理的方法
excludes:和types相反
@NonFinal 设置不为Final,@FieldDefaults和@Value也有这功能
@SuperBuilder 本觉得它是支持到了父类属性的builder构建,但其实,咱们仍是等等吧 目前还很差使
@UtilityClass 工具类 会把全部字段方法static掉,没啥用
@Wither 生成withXXX方法,返回类实例 没啥用,由于还有bug
@Builder和@NoArgsConstructor一块儿使用冲突问题
当咱们这么使用时候:
编译报错:
Error:(17, 1) java: 没法将类 com.sayabc.groupclass.dtos.appoint.TeaPoolLogicalDelDto中的构造器 TeaPoolLogicalDelDto应用到给定类型;
须要: 没有参数
找到: java.lang.Long,java.lang.Long,java.lang.Long,java.lang.Integer
缘由: 实际参数列表和形式参数列表长度不一样
其实缘由很简单,本身点进去看编译后的源码一看便知。
只使用@Builder会自动建立全参构造器。而添加上@NoArgsConstructor后就不会自动产生全参构造器
两种解决方式:
去掉@NoArgsConstructor
添加@AllArgsConstructor(建议使用这种,毕竟无参构造最好保证是有的)
but,枚举值建议这样来就好了,不要加@NoArgsConstructor
@builder注解影响设置默认值的问题
例子以下,原本我是想给age字段直接赋一个默认值的:
没有使用lombok,咱们这么写:
public static void main(String[] args) { Demo demo = new Demo(); System.out.println(demo); //Demo{id=null, age=10} } private static class Demo { private Integer id; private Integer age = 10; //放置默认值年龄 //省略手动书写的get、set、方法和toString方法 @Override public String toString() { return "Demo{" + "id=" + id + ", age=" + age + '}'; } }
咱们发现,这样运行没有问题,默认值也生效了。可是,可是咱们用了强大的lombok,咱们怎么可能还愿意手写get/set呢?关键是,咱们通常状况下还会用到它的@buider注解:
public static void main(String[] args) { Demo demo = new Demo(); System.out.println(demo); //Demo{id=null, age=10} //采用builder构建 这是咱们使用最多的场景吧 Demo demo2 = Demo.builder().build(); System.out.println(demo2); //PeriodAddReq.Demo(id=null, age=null) } @Getter @Setter @Builder @NoArgsConstructor @AllArgsConstructor @ToString private static class Demo { private Integer id; private Integer age = 10; //放置默认值年龄 }
代码简洁了很多。可是咱们却发现一个问题。new出来的对象默认值仍然没有问题,可是buider构建出来的demo2对象,默认值却没有设置进去。这是一个很是隐晦的问题,一不当心,就可能留下一个惊天大坑,因此须要注意
其实在执行编译的时候,idea开发工具已经警告咱们了:
Warning:(51, 25) java: @Builder will ignore the initializing expression entirely. If you want the initializing expression to serve as default, add @Builder.Default. If it is not supposed to be settable during building, make the field final.
方案一:
从它的建议能够看出,把字段标为final就ok了(亲测好用)。但很显然,绝大多数咱们并不但愿他是final的字段。
所以咱们采用第二个方案:
@Getter @Setter @Builder @NoArgsConstructor @AllArgsConstructor @ToString private static class Demo { private Integer id; @Builder.Default private Integer age = 10; //放置默认值年龄 }
lombok考虑到了这种现象,所以咱们只须要在须要设置默认值的字段上面加上 @Builder.Default注解就ok了。
public static void main(String[] args) { Demo demo = new Demo(); System.out.println(demo); //PeriodAddReq.Demo(id=null, age=null) //采用builder构建 这是咱们使用最多的场景吧 Demo demo2 = Demo.builder().build(); System.out.println(demo2); //PeriodAddReq.Demo(id=null, age=10) }
可是咱们坑爹的发现:builder默认值没问题了,可是new出来又有问题了。见鬼啊,
我认为这是lombok的一个大bug,但愿后续版本中可以修复
可是咱们不能由于有这么一个问题,我们就不使用它了。本文主要提醒读者,在使用的时候留心这个问题便可。