Lombok简介
Lombok是一个可以通过简单的注解形式来帮助我们简化消除一些必须有但显得很臃肿的Java代码的工具,通过使用对应的注解,可以在编译源码的时候生成对应的方法。官方地址:https://projectlombok.org/,github地址:https://github.com/rzwitserloot/lombok。
Lombok项目通过添加“处理程序”,使java成为一种更为简单的语言。作为一个Old Java Developer,我们都知道我们经常需要定义一系列的套路,比如定义如下的格式对象。
public class DataExample {
private final String name;
private int age;
private double score;
private String[] tags;
}
我们往往需要定义一系列的Get和Set方法最终展示形式如:
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 getName() {
return this.name;
}
void setAge(int age) {
this.age = age;
}
public int getAge() {
return this.age;
}
public void setScore(double score) {
this.score = score;
}
public double getScore() {
return this.score;
}
public String[] getTags() {
return this.tags;
}
public void setTags(String[] tags) {
this.tags = tags;
}
}
当然我们也可以简化的办法,第一种就是使用IDEA等IDE提供的一键生成的快捷键,第二种就是我们今天介绍的 Lombok项目:
public class DataExample { private final String name; (AccessLevel.PACKAGE) private int age; private double score; private String[] tags; }
这样就可以完成我们的需求,简直是太棒了,仅仅需要几个注解,我们就拥有了完整的GetSet方法,还包含了ToString等方法的生成。
Lombok安装
使用lombok之前需要安装lombok到IDE,否则IDE无法解析lombok注解;首先去官网下载最新的lonbok安装包,网址:https://projectlombok.org/download 有两种安装方式:
1. 双击下载下来的 JAR 包安装 lombok
安装之前先关闭eclipse,然后双击下载的lombok.jar
如果像上图所示提示找不到IDE,则需要手动选择IDE,
出现上面的页面时,点击Install/Update
打开 Eclipse 的 About 页面我们可以看见,如下图所示,说明安装成功
2. eclipse 手动安装 lombok
1) 将 lombok.jar 复制到 eclipse.ini 所在的文件夹目录下
2) 打开 eclipse.ini在最后面插入以下两行并保存:
-Xbootclasspath/a:lombok.jar
-javaagent:lombok.jar
3) 重启eclipse
Lombok使用
1、在工程中引入lombok
在pom.xml中加入lombok依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
</dependency>
2、Lombok注解介绍
1、@Data 注解:
包含了:@ToString,@EqualsAndHashCode, @Getter on all fields, @Setter on all non-final fields, and@RequiredArgsConstructor这些注解
注解在类上;提供类所有属性的 getting 和 setting 方法,此外还提供了equals、canEqual、hashCode、toString 方法
@Data
public class Person {
private final String name;
private int age;
private double score;
private String[] tags;
}
相当于:
由上图类结构可以看出,自动添加了set/get方法,同时还有一个带有final字段参数的构造函数
public Person(String name) {
this.name = name;
}
还有toString、equals、canEqual、hashCode方法
@Override
public String toString() {
return "DataExample(" + this.getName() + ", " + this.getAge() + ", " + this.g etScore() + ", " + Arrays.deepToString(this.getTags()) + ")";
}
protected boolean canEqual(Object other) {
return other instanceof DataExample;
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof DataExample)) return false;
DataExample other = (DataExample) o;
if (!other.canEqual((Object)this)) return false;
if (this.getName() == null ? other.getName() != null : !this.getName().equals(other.getName())) return false;
if (this.getAge() != other.getAge()) return false;
if (Double.compare(this.getScore(), other.getScore()) != 0) return false;
if (!Arrays.deepEquals(this.getTags(), other.getTags())) return false;
return true;
}
@Override
public int hashCode() {
final int PRIME = 59;
int result = 1;
final long temp1 = Double.doubleToLongBits(this.getScore());
result = (result*PRIME) + (this.getName() == null ? 43 : this.getName().hashCode());
result = (result*PRIME) + this.getAge();
result = (result*PRIME) + (int)(temp1 ^ (temp1 >>> 32));
result = (result*PRIME) + Arrays.deepHashCode(this.getTags());
return result;
}
2、@value
@Value 是 @Data 的不可变版本,用作不可变
@Value
public class ValueExample {
String name;
@Wither(AccessLevel.PACKAGE) @NonFinal int age;
double score;
protected String[] tags;
@ToString(includeFieldNames=true)
@Value(staticConstructor="of")
public static class Exercise<T> {
String name;
T value;
}
}
翻译过后为:
public final class ValueExample {
private final String name;
private int age;
private final double score;
protected final String[] tags;
@java.beans.ConstructorProperties({"name", "age", "score", "tags"})
public ValueExample(String name, int age, double score, String[] tags) {
this.name = name;
this.age = age;
this.score = score;
this.tags = tags;
}
public String getName() {
return this.name;
}
public int getAge() {
return this.age;
}
public double getScore() {
return this.score;
}
public String[] getTags() {
return this.tags;
}
@java.lang.Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof ValueExample)) return false;
final ValueExample other = (ValueExample)o;
final Object this$name = this.getName();
final Object other$name = other.getName();
if (this$name == null ? other$name != null : !this$name.equals(other$name)) return false;
if (this.getAge() != other.getAge()) return false;
if (Double.compare(this.getScore(), other.getScore()) != 0) return false;
if (!Arrays.deepEquals(this.getTags(), other.getTags())) return false;
return true;
}
@java.lang.Override
public int hashCode() {
final int PRIME = 59;
int result = 1;
final Object $name = this.getName();
result = result * PRIME + ($name == null ? 43 : $name.hashCode());
result = result * PRIME + this.getAge();
final long $score = Double.doubleToLongBits(this.getScore());
result = result * PRIME + (int)($score >>> 32 ^ $score);
result = result * PRIME + Arrays.deepHashCode(this.getTags());
return result;
}
@java.lang.Override
public String toString() {
return "ValueExample(name=" + getName() + ", age=" + getAge() + ", score=" + getScore() + ", tags=" + Arrays.deepToString(getTags()) + ")";
}
ValueExample withAge(int age) {
return this.age == age ? this : new ValueExample(name, age, score, tags);
}
public static final class Exercise<T> {
private final String name;
private final T value;
private Exercise(String name, T value) {
this.name = name;
this.value = value;
}
public static <T> Exercise<T> of(String name, T value) {
return new Exercise<T>(name, value);
}
public String getName() {
return this.name;
}
public T getValue() {
return this.value;
}
@java.lang.Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof ValueExample.Exercise)) return false;
final Exercise<?> other = (Exercise<?>)o;
final Object this$name = this.getName();
final Object other$name = other.getName();
if (this$name == null ? other$name != null : !this$name.equals(other$name)) return false;
final Object this$value = this.getValue();
final Object other$value = other.getValue();
if (this$value == null ? other$value != null : !this$value.equals(other$value)) return false;
return true;
}
@java.lang.Override
public int hashCode() {
final int PRIME = 59;
int result = 1;
final Object $name = this.getName();
result = result * PRIME + ($name == null ? 43 : $name.hashCode());
final Object $value = this.getValue();
result = result * PRIME + ($value == null ? 43 : $value.hashCode());
return result;
}
@java.lang.Override
public String toString() {
return "ValueExample.Exercise(name=" + getName() + ", value=" + getValue() + ")";
}
}
}
3、@setter、 @Getter
注解在属性上;为属性提供 setting 、getting 方法
public class GetterSetterExample {
@Getter @Setter private int age = 10;
@Setter(AccessLevel.PROTECTED) private String name;
@Override public String toString() {
return String.format("%s (age: %d)", name, age);
}
}
翻译后:
public class GetterSetterExample {
private int age = 10;
private String name;
@Override public String toString() {
return String.format("%s (age: %d)", name, age);
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
protected void setName(String name) {
this.name = name;
}
}
4、@Log
注解在类上;为类提供一个 属性为log 日志对象
该注解,有以下几种可以选择:
@CommonsLog
Creates private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(LogExample.class);
@JBossLog
Creates private static final org.jboss.logging.Logger log = org.jboss.logging.Logger.getLogger(LogExample.class);
@Log
Creates private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(LogExample.class.getName());
@Log4j
Creates private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(LogExample.class);
@Log4j2
Creates private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(LogExample.class);
@Slf4j
Creates private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogExample.class);
@XSlf4j
Creates private static final org.slf4j.ext.XLogger log = org.slf4j.ext.XLoggerFactory.getXLogger(LogExample.class);
5、@Getter(lazy = true)
使用了getter这个annotation可以在实际使用到cached的时候生成cached,同时,Lombok会自动去管理线程安全的问题,不会存在重复赋值的问题。如下代码所示
public class GetterLazyExample {
@Getter(lazy = true)
private final double[] cached = expensive();
public double[] expensive() {
double[] result = new double[10000000];
for (int i = 0; i < result.length; i++) {
result[i] = Math.abs(i);
}
return result;
}
}
6、@ToString
生成toString()方法,默认情况下,它会按顺序(以逗号分隔)打印你的类名称以及每个字段。可以这样设置不包含哪些字段@ToString(exclude = "id") / @ToString(exclude = {"id","name"})
@ToString(exclude="id")
public class ToStringExample {
private static final int STATIC_VAR = 10;
private String name;
private Shape shape = new Square(5, 10);
private String[] tags;
private int id;
public String getName() {
return this.getName();
}
@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;
}
}
}
翻译后:
public class ToStringExample {
private static final int STATIC_VAR = 10;
private String name;
private Shape shape = new Square(5, 10);
private String[] tags;
private int id;
public String getName() {
return this.getName();
}
public static class Square extends Shape {
private final int width, height;
public Square(int width, int height) {
this.width = width;
this.height = height;
}
@Override public String toString() {
return "Square(super=" + super.toString() + ", width=" + this.width + ", height=" + this.height + ")";
}
}
@Override public String toString() {
return "ToStringExample(" + this.getName() + ", " + this.shape + ", " + Arrays.deepToString(this.tags) + ")";
}
}
@ToString(exclude = {"id","name"})
public class User {
private Integer id;
private String name;
private String phone;
}
生成的toString方法为:
public String toString(){
return "User(phone=" + phone + ")";
}
7、@NoArgsConstructor、@AllArgsConstructor、@RequiredArgsConstructor
@NoArgsConstructor生成一个无参构造方法。当类中有final字段没有被初始化时,编译器会报错,此时可用@NoArgsConstructor(force = true),然后就会为没有初始化的final字段设置默认值 0 / false / null。对于具有约束的字段(例如@NonNull字段),不会生成检查或分配,因此请注意,正确初始化这些字段之前,这些约束无效。
@AllArgsConstructor:注解在类上;为类提供一个全参的构造方法
@RequiredArgsConstructor会生成构造方法(可能带参数也可能不带参数),如果带参数,这参数只能是以final修饰的未经初始化的字段,或者是以@NonNull注解的未经初始化的字段
@RequiredArgsConstructor(staticName = "of")会生成一个of()的静态方法,并把构造方法设置为私有的
public static void main(String[] args) throws IOException {
@Cleanup InputStream in = new FileInputStream(args[0]);
@Cleanup OutputStream out = new FileOutputStream(args[1]);
byte[] b = new byte[10000];
while (true) {
int r = in.read(b);
if (r == -1) break;
out.write(b, 0, r);
}
}
相当于:
public static void main(String[] args) throws IOException {
InputStream in = new FileInputStream(args[0]);
try {
OutputStream out = new FileOutputStream(args[1]);
try {
byte[] b = new byte[10000];
while (true) {
int r = in.read(b);
if (r == -1) break;
out.write(b, 0, r);
}
} finally {
if (out != null) {
out.close();
}
}
@Cleanup 存在一个小问题:官网给出了提示,如果你的代码中出现了异常,那么会触发cleanup方法抛出异常,导致把原始异常吞掉,这样就导致你在上层不知道发生了什么事情,这个事情很严重啊,但是如果说你是在调用close方法的时候出了异常,那么Cleanup这个annotation是不会把异常吞掉的。同时,官网也指出,作业也没有找到更好的方式去解决这个问题,如果他们找到了会立刻fix这个问题,否则也就只能等待Java 有可能在Update的时候出现新的解决方案。
java7以后建议使用java自带的try-with-resources语句
9、 @Builder
@Builder注解可以自动生成build()方法,构造一个实例属性不需要单独set
@Builder
@Data
public class BuilderExample {
private int age;
@Singular private Set<String> names;
public static void main(String[] args) throws IOException {
BuilderExample aBuilderExample = BuilderExample.builder().name("1").name("1").name("zs").build();
System.out.println(aBuilderExample);
}
}
上面的例子输出结果为:
其中@Singular注解用于注释集合字段,该字段的命名必须采用复数的形式,在使用build方法的时候,改字段的单数形式可以直接当做非集合的方式进行赋值。
builder是现在比较推崇的一种构建值对象的方式。
可以看下官方示例,对比一下就都明白了
@Builder
public class BuilderExample {
private String name;
private int age;
@Singular private Set<String> occupations;
}
翻译后为
class BuilderExample {
private String name;
private int age;
private Set<String> occupations;
BuilderExample(String name, int age, Set<String> occupations) {
this.name = name;
this.age = age;
this.occupations = occupations;
}
public static BuilderExampleBuilder builder() {
return new BuilderExampleBuilder();
}
public static class BuilderExampleBuilder {
private String name;
private int age;
private java.util.ArrayList<String> occupations;
BuilderExampleBuilder() {
}
public BuilderExampleBuilder name(String name) {
this.name = name;
return this;
}
public BuilderExampleBuilder age(int age) {
this.age = age;
return this;
}
public BuilderExampleBuilder occupation(String occupation) {
if (this.occupations == null) {
this.occupations = new java.util.ArrayList<String>();
}
this.occupations.add(occupation);
return this;
}
public BuilderExampleBuilder occupations(Collection<? extends String> occupations) {
if (this.occupations == null) {
this.occupations = new java.util.ArrayList<String>();
}
this.occupations.addAll(occupations);
return this;
}
public BuilderExampleBuilder clearOccupations() {
if (this.occupations != null) {
this.occupations.clear();
}
return this;
}
public BuilderExample build() {
// complicated switch statement to produce a compact properly sized immutable set omitted.
// go to https://projectlombok.org/features/Singular-snippet.html to see it.
Set<String> occupations = ...;
return new BuilderExample(name, age, occupations);
}
@java.lang.Override
public String toString() {
return "BuilderExample.BuilderExampleBuilder(name = " + this.name + ", age = " + this.age + ", occupations = " + this.occupations + ")";
}
}
}
10、@NonNull
相当于自动加上是否为null的判断
public class NonNullExample {
private String name;
public NonNullExample(@NonNull Person person) {
this.name = person.getName();
}
}
相当于:
public class NonNullExample{
private String name;
public NonNullExample(Person person) {
if (person == null) {
throw new NullPointerException("person");
}
this.name = person.getName();
}
}
Lombok原理
说道 Lombok,我们就得去提到 JSR 269: Pluggable Annotation Processing API (https://www.jcp.org/en/jsr/detail?id=269) 。JSR 269 之前我们也有注解这样的神器,可是我们比如想要做什么必须使用反射,反射的方法局限性较大。首先,它必须定义@Retention为RetentionPolicy.RUNTIME,只能在运行时通过反射来获取注解值,使得运行时代码效率降低。其次,如果想在编译阶段利用注解来进行一些检查,对用户的某些不合理代码给出错误报告,反射的使用方法就无能为力了。而 JSR 269 之后我们可以在 Javac的编译期利用注解做这些事情。所以我们发现核心的区分是在 运行期 还是 编译期。
从上图可知,Annotation Processing 是在解析和生成之间的一个步骤。
上图是 Lombok 处理流程,在Javac 解析成抽象语法树之后(AST), Lombok 根据自己的注解处理器,动态的修改 AST,增加新的节点(所谓代码),最终通过分析和生成字节码。