lombok使用基础教程
前言
lombok是一个编译级别的插件,它可以在项目编译的时候生成一些代码。在很多工具类的项目中都有这个功能。比如dagger。
通俗的说,lombok可以通过注解来标示生成getter settter等代码。我们自然可以通过编译器比如IDEA的Generate生成,为啥要用这个?
在项目开发阶段,一个class的属性是一直变化的,今天可能增加一个字段,明天可能删除一个字段。每次变化都需要修改对应的模板代码。另外,有的class的字段超级多,多到一眼看不完。如果加上模板代码,更难一眼看出来。更有甚者,由于字段太多,想要使用builder来创建。手动创建builder和字段和原来的类夹杂在一起,看起来真的难受。lombok的@Builder即可解决这个问题。
引入
引入就是加入lombok的jar包。
依赖
直接加入依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.20</version>
</dependency>
IDE 中 lombok 插件支持
eclipse 中
下载lombok
下载地址:https://projectlombok.org/downloads/lombok.jar
或者访问官网下载 https://projectlombok.org/
Intell IDEA 中
在IDEA里使用需要添加一个插件。在插件里搜索lombok,安装,重启。
IDEA里需要在设置中启用annotation processors。
基本用法
Getter Setter
最简单的,最常用的,最直观的使用就是getter setter方法。
import java.util.Date;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
public class GetterSetterExample {
@Getter
@Setter
private int age = 10;
@Getter
@Setter
private boolean active;
@Getter
@Setter
private Boolean none;
@Getter
@Setter
private Date date;
@Setter(AccessLevel.PROTECTED)
private String name;
@Override
public String toString() {
return String.format("%s (age: %d)", name, age);
}
@SuppressWarnings("unused")
public static void main(String[] args) {
GetterSetterExample example = new GetterSetterExample();
example.setActive(true);
example.setAge(123);
example.setDate(new Date());
example.setName("abc");
example.setNone(false);
System.out.println(example);
Date date = example.getDate();
Boolean none = example.getNone();
boolean active = example.isActive();
}
}
简单使用没有问题,深入一点可以看到有些特殊设定。比如javadoc.
- Getter声明创建getter方法;
- Setter声明创建setter方法;
- @Setter(AccessLevel.PROTECTED)可以添加参数,指定权限为继承;
- Attention!关于boolean的set前缀都是set,但getter不同,小写的boolean,即基本类型,前缀是is; Boolean,即包装类型,前缀是get;
编译后的结果如下:
import java.io.PrintStream;
import java.util.Date;
public class GetterSetterExample
{
public int getAge()
{
return this.age;
}
public void setAge(int age)
{
this.age = age;
}
private int age = 10;
private boolean active;
private Boolean none;
private Date date;
private String name;
public boolean isActive()
{
return this.active;
}
public void setActive(boolean active)
{
this.active = active;
}
public Boolean getNone()
{
return this.none;
}
public void setNone(Boolean none)
{
this.none = none;
}
public Date getDate()
{
return this.date;
}
public void setDate(Date date)
{
this.date = date;
}
protected void setName(String name)
{
this.name = name;
}
public String toString()
{
return String.format("%s (age: %d)", new Object[] { this.name, Integer.valueOf(this.age) });
}
public static void main(String[] args)
{
GetterSetterExample example = new GetterSetterExample();
example.setActive(true);
example.setAge(123);
example.setDate(new Date());
example.setName("abc");
example.setNone(Boolean.valueOf(false));
System.out.println(example);
Date date = example.getDate();
Boolean none = example.getNone();
boolean active = example.isActive();
}
}
ToString
虽然ToString在生产环境貌似没什么卵用。但是,很多情况下,我们还是需要这个的。因为记log。不想每次看log的时候是一串@地址,那就好好把toString()加上。
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Setter
@ToString(exclude = "id")
public class ToStringExample {
@Getter
private static final int STATIC_VAR = 10;
private String name;
private Shape shape = new Square(5, 10);
private String[] tags;
@Getter
private int id;
@ToString(callSuper = true, includeFieldNames = true)
public static class Square extends Shape {
private final int width, height;
public Square(int width, int height) {
this.width = width;
this.height = height;
}
}
@ToString
public static class Shape {
private int color;
}
public static void main(String[] args) {
final ToStringExample example = new ToStringExample();
example.setId(1);
example.setName("abc");
example.setTags(new String[]{"a", "b", "c"});
final Shape shape = new Square(1, 2);
example.setShape(shape);
System.out.println(example.toString());
}
}
ToString 一些需要注意的地方:
- exclude 属性可以指定不需要在 toString() 方法中输出的属性
- callSupper 属性可以在会调用父类的 toString 方法,在返回中会出现 super=…
- includeFieldNames 属性在 toString 中打印时显示字段的名称,默认为 true
打印结果如下:
ToStringExample(name=abc, shape=ToStringExample.Square(super=ToStringExample.Shape(color=0), width=1, height=2), tags=[a, b, c])
编译后的代码如下:
import java.io.PrintStream;
import java.util.Arrays;
public class ToStringExample
{
private static final int STATIC_VAR = 10;
private String name;
public void setTags(String[] tags)
{
this.tags = tags;
}
public void setId(int id)
{
this.id = id;
}
public void setName(String name)
{
this.name = name;
}
public void setShape(Shape shape)
{
this.shape = shape;
}
public String toString()
{
return "ToStringExample(name=" + this.name + ", shape=" + this.shape + ", tags=" + Arrays.deepToString(this.tags) + ")";
}
public static int getSTATIC_VAR()
{
return 10;
}
private Shape shape = new Square(5, 10);
private String[] tags;
private int id;
public int getId()
{
return this.id;
}
public static class Square
extends ToStringExample.Shape
{
private final int width;
private final int height;
public String toString()
{
return "ToStringExample.Square(super=" + super.toString() + ", width=" + this.width + ", height=" + this.height + ")";
}
public Square(int width, int height)
{
this.width = width;
this.height = height;
}
}
public static class Shape
{
private int color;
public String toString()
{
return "ToStringExample.Shape(color=" + this.color + ")";
}
}
public static void main(String[] args)
{
ToStringExample example = new ToStringExample();
example.setId(1);
example.setName("abc");
example.setTags(new String[] { "a", "b", "c" });
Shape shape = new Square(1, 2);
example.setShape(shape);
System.out.println(example.toString());
}
}
@EqualsAndHashCode
equals()和hashCode()在Java中有着举足轻重的基地作用,虽然通常很少关注。但是,这个必须不可省。不知道有几个可以手写出来的。
import com.sun.org.apache.xalan.internal.xsltc.compiler.sym;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(exclude = { "id", "shape" })
@SuppressWarnings("unused")
public class EqualsAndHashCodeExample {
private transient int transientVar = 10;
private String name;
private double score;
private String[] tags;
private int id;
private ToStringExample.Shape shape = new Square(5, 10);
@EqualsAndHashCode(callSuper=true)
public static class Square extends ToStringExample.Shape {
private final int width, height;
public Square(int width, int height) {
this.width = width;
this.height = height;
}
}
public static void main(String[] args) {
EqualsAndHashCodeExample example = new EqualsAndHashCodeExample();
EqualsAndHashCodeExample example1 = new EqualsAndHashCodeExample();
boolean equals = example.equals(example1);
System.out.println(equals);
boolean b = example.canEqual(example);
System.out.println(b);
int i = example.hashCode();
System.out.println(i);
}
}
注意:
- exclude 属性可以排除一些不需要用于判断两个对象是否 equals 的属性
- callSuper 属性可以用于父类的 equals 方法判断,如果父类的 equals 方法返回false,则子类的equals方法直接返回 false。详见下面编译后的结果。
编译后的结果为:
import java.io.PrintStream;
import java.util.Arrays;
public class EqualsAndHashCodeExample
{
public int hashCode()
{
int PRIME = 59;int result = 1;Object $name = this.name;result = result * 59 + ($name == null ? 43 : $name.hashCode());long $score = Double.doubleToLongBits(this.score);result = result * 59 + (int)($score ^ $score >>> 32);result = result * 59 + Arrays.deepHashCode(this.tags);return result;
}
protected boolean canEqual(Object other)
{
return other instanceof EqualsAndHashCodeExample;
}
public boolean equals(Object o)
{
if (o == this) {
return true;
}
if (!(o instanceof EqualsAndHashCodeExample)) {
return false;
}
EqualsAndHashCodeExample other = (EqualsAndHashCodeExample)o;
if (!other.canEqual(this)) {
return false;
}
Object this$name = this.name;Object other$name = other.name;
if (this$name == null ? other$name != null : !this$name.equals(other$name)) {
return false;
}
if (Double.compare(this.score, other.score) != 0) {
return false;
}
return Arrays.deepEquals(this.tags, other.tags);
}
private transient int transientVar = 10;
private String name;
private double score;
private String[] tags;
private int id;
private ToStringExample.Shape shape = new Square(5, 10);
public static class Square
extends ToStringExample.Shape
{
private final int width;
private final int height;
public int hashCode()
{
int PRIME = 59;int result = super.hashCode();result = result * 59 + this.width;result = result * 59 + this.height;return result;
}
protected boolean canEqual(Object other)
{
return other instanceof Square;
}
public boolean equals(Object o)
{
if (o == this) {
return true;
}
if (!(o instanceof Square)) {
return false;
}
Square other = (Square)o;
if (!other.canEqual(this)) {
return false;
}
if (!super.equals(o)) {
return false;
}
if (this.width != other.width) {
return false;
}
return this.height == other.height;
}
public Square(int width, int height)
{
this.width = width;
this.height = height;
}
}
public static void main(String[] args)
{
EqualsAndHashCodeExample example = new EqualsAndHashCodeExample();
EqualsAndHashCodeExample example1 = new EqualsAndHashCodeExample();
boolean equals = example.equals(example1);
System.out.println(equals);
boolean b = example.canEqual(example);
System.out.println(b);
int i = example.hashCode();
System.out.println(i);
}
}
构造函数@NoArgsConstructor, @RequiredArgsConstructor, @AllArgsConstructor
Java中class的一切起源于构造器。大家最喜欢的还是构造函数创建对象。这里有一点比较坑的是无参构造函数。当你自己添加一个带有参数的构造函数后,无参构造函数则被隐藏。通常也没啥问题,但当你使用jackson反序列化对象的时候就被恶心到了。jackson通过无参构造函数创建对象。因此,当你考虑这个class会用来序列化为json的时候,即必须手动添加一个无参数构造函数。
@NoArgsConstructor
当你想要创建一个valueobject,DDD中的值对象,要求实现Immutable,那么无参数构造器就不合适了。@NoArgsConstructor会生成一个空的构造器。如果你设置了final field,那么编译会报错。如果你强制执行创建无参数构造器。即,@NoArgsConstructor(force = true),那么final的field会初始化为0/false/null。通常适合与@Data集成。
import lombok.NoArgsConstructor;
import lombok.NonNull;
@NoArgsConstructor(force=true)
public class NoArgsExample {
@NonNull
private final String field;
public static void main(String[] args) {
new NoArgsExample();
}
}
- @NonNull 被忽略了
最终生成的代码如下:
import lombok.NonNull;
public class NoArgsExample
{
@NonNull
private final String field = null;
public static void main(String[] args)
{
new NoArgsExample();
}
}
对于final的字段,我认为我不会用空构造器来做这件事。所以,感觉这个参数force=true不要也罢,鸡肋。
@RequiredArgsConstructor
一个class可以有很多属性,但你可能只关心其中的几个字段,那么可以使用@RequiredArgsConstructor。@NonNull将标注这个字段不应为null,初始化的时候会检查是否为空,否则抛出NullPointException。在上面的无参构造函数中被忽略了。那么,对于关注的字段标注@NonNull, @RequiredArgsConstructor则会生成带有这些字段的构造器。
import java.util.Date;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@SuppressWarnings("unused")
public class RequiredArgsExample {
@NonNull
private String field;
private Date date;
private Integer integer;
private int i;
private boolean b;
private Boolean aBoolean;
}
最终生成结果:
import java.util.Date;
import lombok.NonNull;
public class RequiredArgsExample
{
@NonNull
private String field;
private Date date;
private Integer integer;
private int i;
private boolean b;
private Boolean aBoolean;
public RequiredArgsExample(@NonNull String field)
{
if (field == null) {
throw new NullPointerException("field is marked non-null but is null");
}
this.field = field;
}
}
只有@NonNull会生成构造器。其他默认,Java的class初始化默认为null.false,0.
lombok提供了另一种初始化做法,静态初始化。即私有构造器,使用静态方法创建对象。这种做法看起来简单,但通常用的不多。因为静态初始化的东西很难mock,对测试不够友好。
import java.util.Date;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
@SuppressWarnings("unused")
@RequiredArgsConstructor(staticName="of")
public class RequiredArgsStaticExample {
@NonNull
private String field;
private Date date;
private Integer integer;
private int i;
private boolean b;
private Boolean aBoolean;
}
最终生成代码如下:
import java.util.Date;
import lombok.NonNull;
public class RequiredArgsStaticExample
{
@NonNull
private String field;
private Date date;
private Integer integer;
private int i;
private boolean b;
private Boolean aBoolean;
public static RequiredArgsStaticExample of(@NonNull String field)
{
return new RequiredArgsStaticExample(field);
}
private RequiredArgsStaticExample(@NonNull String field)
{
if (field == null) {
throw new NullPointerException("field is marked non-null but is null");
}
this.field = field;
}
}
@AllArgsConstructor
想要初始化所有字段。
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.NonNull;
@AllArgsConstructor(access=AccessLevel.PROTECTED)
public class AllArgsConstructorExample<T> {
@SuppressWarnings("unused")
private int x, y;
@NonNull private T description;
}
最终生成代码如下:
import lombok.NonNull;
public class AllArgsConstructorExample<T>
{
private int x;
private int y;
@NonNull
private T description;
protected AllArgsConstructorExample(int x, int y, @NonNull T description)
{
if (description == null) {
throw new NullPointerException("description is marked non-null but is null");
}
this.x = x;this.y = y;this.description = description;
}
}
必用项 @Data
@Data是一个集合体。包含Getter,Setter,RequiredArgsConstructor,ToString,EqualsAndHashCode
import lombok.AccessLevel;
import lombok.Data;
import lombok.Setter;
import lombok.ToString;
@Data
public class DataExample {
private final String name;
@Setter(AccessLevel.PACKAGE)
private int age;
private double score;
private String[] tags;
@ToString(includeFieldNames=true)
@Data(staticConstructor="of")
public static class Exercise<T> {
private final String name;
private final T valueT;
}
}
最终生成代码如下:
import java.util.Arrays;
public class DataExample
{
private final String name;
private int age;
private double score;
private String[] tags;
public DataExample(String name)
{
this.name = name;
}
public String toString()
{
return "DataExample(name=" + getName() + ", age=" + getAge() + ", score=" + getScore() + ", tags=" + Arrays.deepToString(getTags()) + ")";
}
public int hashCode()
{
int PRIME = 59;int result = 1;Object $name = getName();result = result * 59 + ($name == null ? 43 : $name.hashCode());result = result * 59 + getAge();long $score = Double.doubleToLongBits(getScore());result = result * 59 + (int)($score ^ $score >>> 32);result = result * 59 + Arrays.deepHashCode(getTags());return result;
}
protected boolean canEqual(Object other)
{
return other instanceof DataExample;
}
public boolean equals(Object o)
{
if (o == this) {
return true;
}
if (!(o instanceof DataExample)) {
return false;
}
DataExample other = (DataExample)o;
if (!other.canEqual(this)) {
return false;
}
Object this$name = getName();Object other$name = other.getName();
if (this$name == null ? other$name != null : !this$name.equals(other$name)) {
return false;
}
if (getAge() != other.getAge()) {
return false;
}
if (Double.compare(getScore(), other.getScore()) != 0) {
return false;
}
return Arrays.deepEquals(getTags(), other.getTags());
}
public void setTags(String[] tags)
{
this.tags = tags;
}
public void setScore(double score)
{
this.score = score;
}
public String[] getTags()
{
return this.tags;
}
public double getScore()
{
return this.score;
}
public int getAge()
{
return this.age;
}
public String getName()
{
return this.name;
}
void setAge(int age)
{
this.age = age;
}
public static class Exercise<T>
{
private final String name;
private final T valueT;
public String toString()
{
return "DataExample.Exercise(name=" + getName() + ", valueT=" + getValueT() + ")";
}
public String getName()
{
return this.name;
}
public T getValueT()
{
return this.valueT;
}
public boolean equals(Object o)
{
if (o == this) {
return true;
}
if (!(o instanceof Exercise)) {
return false;
}
Exercise<?> other = (Exercise)o;
if (!other.canEqual(this)) {
return false;
}
Object this$name = getName();Object other$name = other.getName();
if (this$name == null ? other$name != null : !this$name.equals(other$name)) {
return false;
}
Object this$valueT = getValueT();Object other$valueT = other.getValueT();return this$valueT == null ? other$valueT == null : this$valueT.equals(other$valueT);
}
protected boolean canEqual(Object other)
{
return other instanceof Exercise;
}
public int hashCode()
{
int PRIME = 59;int result = 1;Object $name = getName();result = result * 59 + ($name == null ? 43 : $name.hashCode());Object $valueT = getValueT();result = result * 59 + ($valueT == null ? 43 : $valueT.hashCode());return result;
}
private Exercise(String name, T valueT)
{
this.name = name;this.valueT = valueT;
}
public static <T> Exercise<T> of(String name, T valueT)
{
return new Exercise(name, valueT);
}
}
}
不可变对象value object @Value
这个看起来很美好,就是可以帮忙生成一个不可变对象。对于所有的字段都将生成final的。但我感觉有点失控。注解的优势应该是所见即所得,可以通过字面量来传递消息。而@Value字段给字段加final会让人困惑,因为这更改了我们的定义。当我想声明一个Immutable对象的时候,我会显示的给字段加一个限定final。
同@Data, @Value是一个集合体。包含Getter,AllArgsConstructor,ToString,EqualsAndHashCode。
import java.util.Date;
import lombok.NonNull;
import lombok.Value;
@Value
public class ValueExample {
@NonNull
private String id;
private String name;
private boolean active;
private Date createTime;
}
编译后
import java.util.Date;
import lombok.NonNull;
public final class ValueExample
{
@NonNull
private final String id;
private final String name;
private final boolean active;
private final Date createTime;
public ValueExample(@NonNull String id, String name, boolean active, Date createTime)
{
if (id == null) {
throw new NullPointerException("id is marked non-null but is null");
}
this.id = id;this.name = name;this.active = active;this.createTime = createTime;
}
public String toString()
{
return "ValueExample(id=" + getId() + ", name=" + getName() + ", active=" + isActive() + ", createTime=" + getCreateTime() + ")";
}
public int hashCode()
{
int PRIME = 59;int result = 1;Object $id = getId();result = result * 59 + ($id == null ? 43 : $id.hashCode());Object $name = getName();result = result * 59 + ($name == null ? 43 : $name.hashCode());result = result * 59 + (isActive() ? 79 : 97);Object $createTime = getCreateTime();result = result * 59 + ($createTime == null ? 43 : $createTime.hashCode());return result;
}
public boolean equals(Object o)
{
if (o == this) {
return true;
}
if (!(o instanceof ValueExample)) {
return false;
}
ValueExample other = (ValueExample)o;Object this$id = getId();Object other$id = other.getId();
if (this$id == null ? other$id != null : !this$id.equals(other$id)) {
return false;
}
Object this$name = getName();Object other$name = other.getName();
if (this$name == null ? other$name != null : !this$name.equals(other$name)) {
return false;
}
if (isActive() != other.isActive()) {
return false;
}
Object this$createTime = getCreateTime();Object other$createTime = other.getCreateTime();return this$createTime == null ? other$createTime == null : this$createTime.equals(other$createTime);
}
public Date getCreateTime()
{
return this.createTime;
}
public boolean isActive()
{
return this.active;
}
public String getName()
{
return this.name;
}
@NonNull
public String getId()
{
return this.id;
}
}
最喜欢的项 @Builder
对于喜欢builder模式的人来说,声明式简化对象创建流程让一切看得美好。但是,手动复制字段,手动创建方法很让人不喜。@Builder解决了刚需。
import java.util.Date;
import java.util.Set;
import org.junit.Assert;
import lombok.Builder;
import lombok.Data;
import lombok.NonNull;
import lombok.Singular;
@Data
@Builder(toBuilder = true)
public class BuilderExample {
@NonNull
private String id;
private String name;
private boolean active;
private Date createTime;
@Singular
private Set<String> occupations;
public static void main(String[] args) {
BuilderExample builer = BuilderExample.builder().active(true)
.name("name")
.id("id")
.createTime(new Date())
.occupation("1")
.occupation("2")
.build();
Assert.assertEquals(2, builer.getOccupations().size());
}
}
这才是我们想要的建造者。对应生成的代码为:
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.Set;
import lombok.NonNull;
import org.junit.Assert;
public class BuilderExample
{
@NonNull
private String id;
private String name;
private boolean active;
private Date createTime;
private Set<String> occupations;
public String toString()
{
return "BuilderExample(id=" + getId() + ", name=" + getName() + ", active=" + isActive() + ", createTime=" + getCreateTime() + ", occupations=" + getOccupations() + ")";
}
public int hashCode()
{
int PRIME = 59;int result = 1;Object $id = getId();result = result * 59 + ($id == null ? 43 : $id.hashCode());Object $name = getName();result = result * 59 + ($name == null ? 43 : $name.hashCode());result = result * 59 + (isActive() ? 79 : 97);Object $createTime = getCreateTime();result = result * 59 + ($createTime == null ? 43 : $createTime.hashCode());Object $occupations = getOccupations();result = result * 59 + ($occupations == null ? 43 : $occupations.hashCode());return result;
}
protected boolean canEqual(Object other)
{
return other instanceof BuilderExample;
}
public boolean equals(Object o)
{
if (o == this) {
return true;
}
if (!(o instanceof BuilderExample)) {
return false;
}
BuilderExample other = (BuilderExample)o;
if (!other.canEqual(this)) {
return false;
}
Object this$id = getId();Object other$id = other.getId();
if (this$id == null ? other$id != null : !this$id.equals(other$id)) {
return false;
}
Object this$name = getName();Object other$name = other.getName();
if (this$name == null ? other$name != null : !this$name.equals(other$name)) {
return false;
}
if (isActive() != other.isActive()) {
return false;
}
Object this$createTime = getCreateTime();Object other$createTime = other.getCreateTime();
if (this$createTime == null ? other$createTime != null : !this$createTime.equals(other$createTime)) {
return false;
}
Object this$occupations = getOccupations();Object other$occupations = other.getOccupations();return this$occupations == null ? other$occupations == null : this$occupations.equals(other$occupations);
}
public void setOccupations(Set<String> occupations)
{
this.occupations = occupations;
}
public void setCreateTime(Date createTime)
{
this.createTime = createTime;
}
public void setActive(boolean active)
{
this.active = active;
}
public void setName(String name)
{
this.name = name;
}
public void setId(@NonNull String id)
{
if (id == null) {
throw new NullPointerException("id is marked non-null but is null");
}
this.id = id;
}
public Set<String> getOccupations()
{
return this.occupations;
}
public Date getCreateTime()
{
return this.createTime;
}
public boolean isActive()
{
return this.active;
}
public String getName()
{
return this.name;
}
@NonNull
public String getId()
{
return this.id;
}
public static class BuilderExampleBuilder
{
private String id;
private String name;
private boolean active;
private Date createTime;
private ArrayList<String> occupations;
public String toString()
{
return "BuilderExample.BuilderExampleBuilder(id=" + this.id + ", name=" + this.name + ", active=" + this.active + ", createTime=" + this.createTime + ", occupations=" + this.occupations + ")";
}
public BuilderExample build()
{
Set<String> occupations;
Set<String> occupations;
Set<String> occupations;
switch (this.occupations == null ? 0 : this.occupations.size())
{
case 0:
occupations = Collections.emptySet(); break;
case 1:
occupations = Collections.singleton((String)this.occupations.get(0)); break;
default:
occupations = new LinkedHashSet(this.occupations.size() < 1073741824 ? 1 + this.occupations.size() + (this.occupations.size() - 3) / 3 : 2147483647);occupations.addAll(this.occupations);occupations = Collections.unmodifiableSet(occupations);
}
return new BuilderExample(this.id, this.name, this.active, this.createTime, occupations);
}
public BuilderExampleBuilder clearOccupations()
{
if (this.occupations != null) {
this.occupations.clear();
}
return this;
}
public BuilderExampleBuilder occupations(Collection<? extends String> occupations)
{
if (this.occupations == null) {
this.occupations = new ArrayList();
}
this.occupations.addAll(occupations);return this;
}
public BuilderExampleBuilder occupation(String occupation)
{
if (this.occupations == null) {
this.occupations = new ArrayList();
}
this.occupations.add(occupation);return this;
}
public BuilderExampleBuilder createTime(Date createTime)
{
this.createTime = createTime;return this;
}
public BuilderExampleBuilder active(boolean active)
{
this.active = active;return this;
}
public BuilderExampleBuilder name(String name)
{
this.name = name;return this;
}
public BuilderExampleBuilder id(@NonNull String id)
{
if (id == null) {
throw new NullPointerException("id is marked non-null but is null");
}
this.id = id;return this;
}
}
public BuilderExampleBuilder toBuilder()
{
BuilderExampleBuilder builder = new BuilderExampleBuilder().id(this.id).name(this.name).active(this.active).createTime(this.createTime);
if (this.occupations != null) {
builder.occupations(this.occupations);
}
return builder;
}
public static BuilderExampleBuilder builder()
{
return new BuilderExampleBuilder();
}
BuilderExample(@NonNull String id, String name, boolean active, Date createTime, Set<String> occupations)
{
if (id == null) {
throw new NullPointerException("id is marked non-null but is null");
}
this.id = id;this.name = name;this.active = active;this.createTime = createTime;this.occupations = occupations;
}
public static void main(String[] args)
{
BuilderExample builer = builder().active(true)
.name("name")
.id("id")
.createTime(new Date())
.occupation("1")
.occupation("2")
.build();
Assert.assertEquals(2L, builer.getOccupations().size());
}
}
@Slf4j
日志相关的注解。这里以 @Slf4j 为例。
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class LogExample {
public static void main(String[] args) {
log.error("Something else is wrong here");
}
}
这里需要添加 slf4j 以及它的实现类的依赖
<!-- @Slf4j 支持 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
编译后
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LogExample
{
private static final Logger log = LoggerFactory.getLogger(LogExample.class);
public static void main(String[] args)
{
log.error("Something else is wrong here");
}
}
val
可以使用 val 声明本地变量的类型而不用写出它的实际类型,它实际的类型将由初始化的表达式推断。
import java.util.HashMap;
import lombok.val;
public class ValExample {
public static void main(String[] args) {
// lombok.val 使用
val map = new HashMap<Integer, String>();
map.put(0, "zero");
map.put(5, "five");
for(val entry : map.entrySet()) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
}
}
编译后
import java.io.PrintStream;
import java.util.HashMap;
import java.util.Map.Entry;
public class ValExample
{
public static void main(String[] args)
{
HashMap<Integer, String> map = new HashMap();
map.put(Integer.valueOf(0), "zero");
map.put(Integer.valueOf(5), "five");
for (Map.Entry<Integer, String> entry : map.entrySet()) {
System.out.println(entry.getKey() + ":" + (String)entry.getValue());
}
}
}
Lombok工作原理分析
总结
lombok还提供了其他几个注解,以及还有好多内置的参数没有讲解。但是,根据2-8原理,我们根本不需要。上面这几个足够了。更多的注解只会增加理解阅读难度。
会发现在Lombok使用的过程中,只需要添加相应的注解,无需再为此写任何代码。自动生成的代码到底是如何产生的呢?
核心之处就是对于注解的解析上。JDK5引入了注解的同时,也提供了两种解析方式。
-
运行时解析
运行时能够解析的注解,必须将@Retention设置为RUNTIME,这样就可以通过反射拿到该注解。java.lang,reflect反射包中提供了一个接口AnnotatedElement,该接口定义了获取注解信息的几个方法,Class、Constructor、Field、Method、Package等都实现了该接口,对反射熟悉的朋友应该都会很熟悉这种解析方式。 -
编译时解析
编译时解析有两种机制,分别简单描述下:
1)Annotation Processing Tool
apt自JDK5产生,JDK7已标记为过期,不推荐使用,JDK8中已彻底删除,自JDK6开始,可以使用Pluggable Annotation Processing API来替换它,apt被替换主要有2点原因:
- api都在com.sun.mirror非标准包下
- 没有集成到javac中,需要额外运行
2)Pluggable Annotation Processing API
JSR 269自JDK6加入,作为apt的替代方案,它解决了apt的两个问题,javac在执行的时候会调用实现了该API的程序,这样我们就可以对编译器做一些增强,这时javac执行的过程如下:
Lombok本质上就是一个实现了“JSR 269 API”的程序。在使用javac的过程中,它产生作用的具体流程如下:
- javac对源代码进行分析,生成了一棵抽象语法树(AST)
- 运行过程中调用实现了“JSR 269 API”的Lombok程序
- 此时Lombok就对第一步骤得到的AST进行处理,找到@Data注解所在类对应的语法树(AST),然后修改该语法树(AST),增加getter和setter方法定义的相应树节点
- javac使用修改后的抽象语法树(AST)生成字节码文件,即给class增加新的节点(代码块)
拜读了Lombok源码,对应注解的实现都在HandleXXX中,比如@Getter注解的实现时HandleGetter.handle()。还有一些其它类库使用这种方式实现,比如Google Auto、Dagger等等。
4. Lombok的优缺点
优点:
- 能通过注解的形式自动生成构造器、getter/setter、equals、hashcode、toString等方法,提高了一定的开发效率
- 让代码变得简洁,不用过多的去关注相应的方法
- 属性做修改时,也简化了维护为这些属性所生成的getter/setter方法等
缺点:
- 不支持多种参数构造器的重载
- 虽然省去了手动创建getter/setter方法的麻烦,但大大降低了源代码的可读性和完整性,降低了阅读源代码的舒适度