Lombok介绍、操作方法和归纳转载

原创
小哥 3年前 (2022-11-11) 阅读数 94 #大杂烩

同步启动: http://www.yuanrengu.com/index.php/20180324.html

1 Lombok背景介绍

官方介绍如下:

Project Lombok makes java a spicier language by adding handlers that know how to build and compile simple, boilerplate-free, not-quite-java code.

一般意义是Lombok通过添加一些“处理程序”,您可以。java变得简洁和快速。

2 Lombok使用方法

Lombok可以简化为简单注释的形式java提高开发人员开发效率的代码。例如,通常需要在开发中编写。javabean,添加相应的getter/setter,可能还必须编写构造函数,equals等等,并且需要维护,当有很多属性时会出现大量属性。getter/setter这些方法看起来很长,没有太多的技术含量。一旦修改了属性,就很容易犯忘记修改相应方法的错误。

Lombok构造函数可以在编译时通过注释为属性自动生成,getter/setter、equals、hashcode、toString方法神奇的是没有getter和setter方法,但在编译的字节码文件中有。getter和setter方法这避免了手动重新构建代码的麻烦,并使其看起来更加简洁。

Lombok使用和参考jar和套餐一样,可以在官方网站上找到( https://projectlombok.org/download )下载jar包装,也可以使用。maven添加依赖项:

org.projectlombok lombok 1.16.20 provided

接下来,让我们分析Lombok中注释的具体用法。

2.1 @Data

@Data为类上的类的所有属性自动生成注释。setter/getter、equals、canEqual、hashCode、toString方法,作为final属性,则不会为该属性生成。setter方法。

官方示例如下:

import lombok.AccessLevel; import lombok.Setter; import lombok.Data; 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 { private final String name; private final T value; } }

如不使用Lombok,则实现如下:

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 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; }

@Override public String toString() { return "DataExample(" + this.getName() + ", " + this.getAge() + ", " + this.getScore() + ", " + 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 = (resultPRIME) + (this.getName() == null ? 43 : this.getName().hashCode()); result = (resultPRIME) + this.getAge(); result = (resultPRIME) + (int)(temp1 ^ (temp1 >>> 32)); result = (resultPRIME) + Arrays.deepHashCode(this.getTags()); return result; }

public static class Exercise { private final String name; private final T value;

private Exercise(String name, T value) {
  this.name = name;
  this.value = value;
}

public static  Exercise of(String name, T value) {
  return new Exercise(name, value);
}

public String getName() {
  return this.name;
}

public T getValue() {
  return this.value;
}

@Override public String toString() {
  return "Exercise(name=" + this.getName() + ", value=" + this.getValue() + ")";
}

protected boolean canEqual(Object other) {
  return other instanceof Exercise;
}

@Override public boolean equals(Object o) {
  if (o == this) return true;
  if (!(o instanceof Exercise)) return false;
  Exercise other = (Exercise) o;
  if (!other.canEqual((Object)this)) return false;
  if (this.getName() == null ? other.getValue() != null : !this.getName().equals(other.getName())) return false;
  if (this.getValue() == null ? other.getValue() != null : !this.getValue().equals(other.getValue())) return false;
  return true;
}

@Override public int hashCode() {
  final int PRIME = 59;
  int result = 1;
  result = (result*PRIME) + (this.getName() == null ? 43 : this.getName().hashCode());
  result = (result*PRIME) + (this.getValue() == null ? 43 : this.getValue().hashCode());
  return result;
}

} }

2.2 @Getter/@Setter

如果觉得@Data太残忍了(因为@Data集合了@ToString、@EqualsAndHashCode、@Getter/@Setter、@RequiredArgsConstructor所有特征)都不够精细,无法使用。@Getter/@Setter注释,可以为属性上的相应属性自动生成Getter/Setter方法,示例如下:

import lombok.AccessLevel; import lombok.Getter; import lombok.Setter;

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); } }

如果未使用Lombok:

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; } }

2.3 @NonNull

此注释用于属性或构造函数,Lombok生成非空声明,可用于检查参数并帮助避免空指针。

示例如下:

import lombok.NonNull;

public class NonNullExample extends Something { private String name;

public NonNullExample(@NonNull Person person) { super("Hello"); this.name = person.getName(); } }

不使用Lombok:

import lombok.NonNull;

public class NonNullExample extends Something { private String name;

public NonNullExample(@NonNull Person person) { super("Hello"); if (person == null) { throw new NullPointerException("person"); } this.name = person.getName(); } }

2.4 @Cleanup

这个注释可以帮助我们自动调用它。close()方法,大大简化了代码。

示例如下:

import lombok.Cleanup; import java.io.*;

public class CleanupExample { 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); } } }

如不使用Lombok,需要以下内容:

import java.io.*;

public class CleanupExample { 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(); } } } finally { if (in != null) { in.close(); } } } }

2.5 @EqualsAndHashCode

默认情况下,所有非静态(non-static)和非瞬态(non-transient)要生成的属性equals和hasCode,也可以通过。exclude注释以排除某些属性。

示例如下:

import lombok.EqualsAndHashCode;

@EqualsAndHashCode(exclude={"id", "shape"}) public class EqualsAndHashCodeExample { private transient int transientVar = 10; private String name; private double score; private Shape shape = new Square(5, 10); private String[] tags; private int id;

public String getName() { return this.name; }

@EqualsAndHashCode(callSuper=true) public static class Square extends Shape { private final int width, height;

public Square(int width, int height) {
  this.width = width;
  this.height = height;
}

} }

2.6 @ToString

类使用@ToString注解,Lombok将生成toString()方法,默认情况下,类名和所有属性(按定义属性的顺序)都将输出并用逗号分隔。

通过将 includeFieldNames 参数设为true,您可以清楚地输出。toString()属性这是不是有点绕道,通过代码会更清楚。

使用Lombok的示例:

import lombok.ToString;

@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;
}

} }

不使用Lombok的示例如下:

import java.util.Arrays;

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) + ")"; } }

2.7 @NoArgsConstructor, @RequiredArgsConstructor and @AllArgsConstructor

无参数构造函数、部分参数构造函数、完全参数构造函数。Lombok无法重载多个参数构造函数。

Lombok示例代码如下:

import lombok.AccessLevel; import lombok.RequiredArgsConstructor; import lombok.AllArgsConstructor; import lombok.NonNull;

@RequiredArgsConstructor(staticName = "of") @AllArgsConstructor(access = AccessLevel.PROTECTED) public class ConstructorExample { private int x, y; @NonNull private T description;

@NoArgsConstructor public static class NoArgsExample { @NonNull private String field; } }

不使用Lombok的示例如下:

public class ConstructorExample { private int x, y; @NonNull private T description;

private ConstructorExample(T description) { if (description == null) throw new NullPointerException("description"); this.description = description; }

public static ConstructorExample of(T description) { return new ConstructorExample(description); }

@java.beans.ConstructorProperties({"x", "y", "description"}) protected ConstructorExample(int x, int y, T description) { if (description == null) throw new NullPointerException("description"); this.x = x; this.y = y; this.description = description; }

public static class NoArgsExample { @NonNull private String field;

public NoArgsExample() {
}

} }

3 Lombok工作原理分析

会发现在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其工作的具体过程如下:

  1. javac分析源代码以生成抽象语法树(AST)
  2. 运行期间的调用实现“JSR 269 API”的Lombok程序
  3. 此时Lombok这是第一步。AST进程,查找@Data与注释所在的类相对应的语法树(AST),然后修改语法树(AST),增加getter和setter方法定义的相应树节点。
  4. javac使用修改后的抽象语法树(AST)生成给定的字节码文件。class添加新节点(代码块)

拜读了Lombok源代码,相应注释的实现在中。HandleXXX中,比如@Getter实现注释时HandleGetter.handle()。还有其他类库使用这种方法,例如 Google AutoDagger 等等。

  1. Lombok的优缺点

优点:

  1. 构造函数可以以注释的形式自动生成,getter/setter、equals、hashcode、toString以及其他方法,在一定程度上提高了开发效率。
  2. 使代码简明扼要,不要过分关注相应的方法。
  3. 修改属性时,还可以简化为这些属性生成的的维护。getter/setter方法等

缺点:

  1. 不支持重载多参数构造函数。

  2. 尽管省略了手动创建getter/setter该方法很繁琐,但大大降低了源代码的可读性和完整性,降低了阅读源代码的舒适度。

  3. 总结

Lombok尽管有许多优点,Lombok更类似于IDE插件,项目也需要依赖相应的jar包。Lombok依赖jar包是用其注释编译的。为什么它类似于插件?因为在使用中,eclipse或IntelliJ IDEA所有这些都需要安装相应的插件,这些插件在编译器编译期间运行。AST(抽象语法树)改变字节码的生成,改变方向意味着它正在改变。java语法这不像spring依赖注入或mybatis的ORM这也是一个运行时特性,但却是一个编译时特性。在这里,我个人感觉最不舒服的地方是对插件的依赖!因为Lombok只需省去手动生成代码的麻烦,IDE有快捷键可帮助生成getter/setter它也很方便。

知乎上有一位大神表达了他的权利Lombok一些观点:

这是一个便宜的插件,不推荐使用。JAVA随着今天的发展,各种插件层出不穷。如何识别各种插件的优缺点?可以优化设计架构并提高应用程序性能 , 实现高度封装的可扩展性..., 像lombok这种插件,就像这种插件一样,不仅改变了你编写源代码的方式,事实上,如果你编写的代码少了怎么办? 如果JAVA家里到处都是这样的东西。它只是一块被金属色覆盖的粪便。它迟早会被其他语言取代。

虽然措辞粗暴,但原因并不粗暴。想象一下,一个项目中有许多类似的项目。Lombok我认为这样的插件确实会大大降低阅读源代码的舒适度。

虽然不建议在酒店使用。getter/setter编写一些业务代码,但在多年项目的现实世界中,有时通过提供getter/setter添加一点业务代码可以大大简化某些业务场景的代码。所谓的权衡可能是放弃某些规范和极大的便利。

我现在非常确信,任何编程语言或插件都只是一种工具。即使工具很强大,也取决于使用它的人,就像小米步枪仍然可以赢得飞机和大炮一样。结合具体业务场景和项目实际情况,不必盲目追求高科技,适合才是王道。

Lombok它有其独特的优点,但也有其回避的缺点,熟悉其优点和缺点,在实战中灵活运用才是王道。

参考:

https://projectlombok.org/features/

https://github.com/rzwitserloot/lombok?spm=a2c4e.11153940.blogcont59972.5.2aeb6d32hayLHv

https://www.zhihu.com/question/42348457

https://blog.csdn.net/ghsau/article/details/52334762

版权声明

所有资源都来源于爬虫采集,如有侵权请联系我们,我们将立即删除