Introduction to Project Lombok – 龙目岛项目简介

最后修改: 2016年 6月 12日

中文/混合/英文(键盘快捷键:t)

1. Avoid Repetitive Code

1.避免重复的代码

Java is a great language, but it can sometimes get too verbose for common tasks we have to do in our code or compliance with some framework practices. This often doesn’t bring any real value to the business side of our programs, and that’s where Lombok comes in to make us more productive.

Java是一门伟大的语言,但对于我们在代码中必须要做的普通任务或遵守一些框架惯例来说,它有时会变得过于冗长。这往往不能给我们的程序的业务方面带来任何真正的价值,而这正是Lombok出现的地方,它能使我们更有效率。

The way it works is by plugging into our build process and auto-generating Java bytecode into our .class files as per a number of project annotations we introduce in our code.

它的工作方式是插入我们的构建过程,并根据我们在代码中引入的一些项目注解,自动生成Java字节码到我们的.class文件。

Including it in our builds, in whichever system we’re using, is very straight forward. Project Lombok’s project page has detailed instructions on the specifics. Most of my projects are maven based, so I just typically drop their dependency in the provided scope and I’m good to go:

无论我们使用哪种系统,将其纳入我们的构建中,都是非常直接的。Project Lombok的项目页面有详细的具体说明。我的大多数项目都是基于maven的,所以我通常只需把它们的依赖关系放在provided范围内,就可以了。

<dependencies>
    ...
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.20</version>
        <scope>provided</scope>
    </dependency>
    ...
</dependencies>

We can check for the most recent available version here.

我们可以检查最新的可用版本这里

Note that depending on Lombok won’t make users of our .jars depend on it as well, as it is a pure build dependency, not runtime.

注意,对Lombok的依赖不会使我们的.jars用户也依赖它,因为它是一个纯粹的构建依赖,而不是运行时。

2. Getters/Setters, Constructors – So Repetitive

2.获取器/设置器,构造器 – 如此重复

Encapsulating object properties via public getter and setter methods is such a common practice in the Java world, and lots of frameworks rely on this “Java Bean” pattern extensively (a class with an empty constructor and get/set methods for “properties”).

通过公共的getter和setter方法来封装对象属性是Java世界中的一种常见做法,很多框架都广泛依赖这种 “Java Bean “模式(一个具有空的构造函数和get/set方法的 “属性 “类)。

This is so common that most IDE’s support auto-generating code for these patterns (and more). However, this code needs to live in our sources and be maintained when a new property is added or a field renamed.

这很常见,以至于大多数IDE都支持为这些模式(以及更多)自动生成代码。然而,这段代码需要存在于我们的源代码中,并在添加新的属性或重命名一个字段时得到维护。

Let’s consider this class we want to use as a JPA entity:

让我们考虑一下这个我们想作为JPA实体使用的类。

@Entity
public class User implements Serializable {

    private @Id Long id; // will be set when persisting

    private String firstName;
    private String lastName;
    private int age;

    public User() {
    }

    public User(String firstName, String lastName, int age) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
    }

    // getters and setters: ~30 extra lines of code
}

This is a rather simple class, but imagine if we had added the extra code for getters and setters. We would have ended up with a definition where there would be more boilerplate zero-value code than the relevant business information: “a User has first and last names, and age.”

这是一个相当简单的类,但是想象一下,如果我们为getters和setters添加了额外的代码。我们最终会得到一个定义,其中有更多的模板零值代码,而不是相关的业务信息。”一个用户有姓和名,以及年龄”。

Let’s now Lombok-ize this class:

现在让我们Lombok-ize这个类。

@Entity
@Getter @Setter @NoArgsConstructor // <--- THIS is it
public class User implements Serializable {

    private @Id Long id; // will be set when persisting

    private String firstName;
    private String lastName;
    private int age;

    public User(String firstName, String lastName, int age) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
    }
}

By adding the @Getter and @Setter annotations, we told Lombok to generate these for all the fields of the class. @NoArgsConstructor will lead to an empty constructor generation.

通过添加@Getter@Setter注解,我们告诉Lombok为该类的所有字段生成这些。@NoArgsConstructor将导致生成一个空的构造函数。

Note that this is the whole class code, we’re not omitting anything unlike the version above with the // getters and setters comment. For a three relevant attributes class, this is a significant saving in code!

请注意,这是整个类的代码,我们没有省略任何东西,与上面带有// getters和setters注释的版本不同。对于一个有三个相关属性的类来说,这是一个相当大的代码节省!

If we further add attributes (properties) to our User class, the same will happen; we apply the annotations to the type itself so they will mind all fields by default.

如果我们进一步向我们的User类添加属性(properties),也会发生同样的情况;我们将注解应用于类型本身,所以它们会默认介意所有字段。

What if we want to refine the visibility of some properties? For example, if we want to keep our entities’ id field modifiers package or protected visible because they are expected to be read, but not explicitly set by application code, we can just use a finer grained @Setter for this particular field:

如果我们想细化一些属性的可见性怎么办?例如,如果我们想保持我们实体的id字段修改器packageprotected的可见性,因为它们被期望被读取,但不被应用程序代码明确设置,我们就可以为这个特定字段使用更细化的@Setter

private @Id @Setter(AccessLevel.PROTECTED) Long id;

3. Lazy Getter

3.懒惰的获取者

Applications often need to perform expensive operations and save the results for subsequent use.

应用程序经常需要执行昂贵的操作,并保存结果供以后使用。

For instance, let’s say we need to read static data from a file or a database. It’s generally good practice to retrieve this data once, and then cache it to allow in-memory reads within the application. This saves the application from repeating the expensive operation.

例如,假设我们需要从一个文件或数据库中读取静态数据。一般来说,好的做法是检索一次这种数据,然后将其缓存起来,以便在应用程序中进行内存读取。这可以使应用程序避免重复昂贵的操作。

Another common pattern is to retrieve this data only when it’s first needed. In other words, we only get the data when we call the corresponding getter the first time. We call this lazy-loading.

另一种常见的模式是:只有在第一次需要时才检索这些数据。换句话说,我们只在第一次调用相应的getter时获取数据。我们把这称为lazy-loading

Let’s suppose that this data is cached as a field inside a class. The class must now make sure that any access to this field returns the cached data. One possible way to implement such a class is to make the getter method retrieve the data only if the field is null. We call this a lazy getter.

让我们假设这个数据被缓存在一个类里面的一个字段。这个类现在必须确保对这个字段的任何访问都能返回缓存的数据。实现这样一个类的一个可能的方法是让getter方法只在字段为null时才检索到数据。我们把这称为lazy getter

Lombok makes this possible with the lazy parameter in the @Getter annotation we saw above.

Lombok通过我们上面看到的@Getter注解中的lazy参数使之成为可能。

For example, consider this simple class:

例如,考虑这个简单的类。

public class GetterLazy {

    @Getter(lazy = true)
    private final Map<String, Long> transactions = getTransactions();

    private Map<String, Long> getTransactions() {

        final Map<String, Long> cache = new HashMap<>();
        List<String> txnRows = readTxnListFromFile();

        txnRows.forEach(s -> {
            String[] txnIdValueTuple = s.split(DELIMETER);
            cache.put(txnIdValueTuple[0], Long.parseLong(txnIdValueTuple[1]));
        });

        return cache;
    }
}

This reads some transactions from a file into a Map. Since the data in the file doesn’t change, we’ll cache it once and allow access via a getter.

这是从一个文件中读取一些事务到一个Map。由于文件中的数据没有变化,我们将对其进行一次缓存,并允许通过一个getter进行访问。

If we now look at the compiled code of this class, we’ll see a getter method which updates the cache if it was null and then returns the cached data:

如果我们现在看一下这个类的编译代码,我们会看到一个getter方法,如果缓存是null,则更新缓存,然后返回缓存的数据

public class GetterLazy {

    private final AtomicReference<Object> transactions = new AtomicReference();

    public GetterLazy() {
    }

    //other methods

    public Map<String, Long> getTransactions() {
        Object value = this.transactions.get();
        if (value == null) {
            synchronized(this.transactions) {
                value = this.transactions.get();
                if (value == null) {
                    Map<String, Long> actualValue = this.readTxnsFromFile();
                    value = actualValue == null ? this.transactions : actualValue;
                    this.transactions.set(value);
                }
            }
        }

        return (Map)((Map)(value == this.transactions ? null : value));
    }
}

It’s interesting to point out that Lombok wrapped the data field in an AtomicReference. This ensures atomic updates to the transactions field. The getTransactions() method also makes sure to read the file if transactions is null.

有趣的是,Lombok将数据字段包裹在一个a href=”/java-atomic-variables”>AtomicReference.这确保了对transactions字段的原子更新。getTransactions()方法还确保在transactionsnull时读取该文件。

We discourage using the AtomicReference transactions field directly from within the class. We recommend using the getTransactions() method for accessing the field.

我们不鼓励从类中直接使用AtomicReference事务字段。我们建议使用getTransactions()方法来访问该字段。

For this reason, if we use another Lombok annotation like ToString in the same class, it will use getTransactions() instead of directly accessing the field.

出于这个原因,如果我们在同一个类中使用另一个Lombok注解,如ToString,它将使用getTransactions()而不是直接访问字段。

4. Value Classes/DTO’s

4.价值类/DTO的

There are many situations in which we want to define a data type with the sole purpose of representing complex “values” as “Data Transfer Objects,” most of the time in the form of immutable data structures we build once and never want to change.

在很多情况下,我们想定义一种数据类型,其唯一目的是将复杂的 “值 “表示为 “数据传输对象”,大多数情况下是以不可改变的数据结构的形式,我们建立一次就永远不想再改变。

We design a class to represent a successful login operation. We want all fields to be non-null and objects be immutable so that we can thread-safely access its properties:

我们设计一个类来表示一个成功的登录操作。我们希望所有的字段都是非空的,并且对象是不可变的,这样我们就能以线程安全的方式访问其属性。

public class LoginResult {

    private final Instant loginTs;

    private final String authToken;
    private final Duration tokenValidity;
    
    private final URL tokenRefreshUrl;

    // constructor taking every field and checking nulls

    // read-only accessor, not necessarily as get*() form
}

Again, the amount of code we would have to write for the commented sections would be of a much larger volume than the information we want to encapsulate needs to be. We can use Lombok to improve this:

同样,我们为注释部分所写的代码量会比我们想要封装的信息所需的量大得多。我们可以使用Lombok来改善这一点。

@RequiredArgsConstructor
@Accessors(fluent = true) @Getter
public class LoginResult {

    private final @NonNull Instant loginTs;

    private final @NonNull String authToken;
    private final @NonNull Duration tokenValidity;
    
    private final @NonNull URL tokenRefreshUrl;

}

Once we add the @RequiredArgsConstructor annotation, we’ll get a constructor for all the final fields int the class, just as we declared them. Adding @NonNull to attributes makes our constructor check for nullability and throw NullPointerExceptions accordingly. This would also happen if the fields were non-final and we added @Setter for them.

一旦我们添加了@RequiredArgsConstructor注解,我们将得到一个用于类中所有最终字段的构造函数,就像我们声明的那样。为属性添加@NonNull使我们的构造函数检查是否为空并相应地抛出NullPointerExceptions。如果字段是非最终的,并且我们为它们添加了@Setter,这也会发生。

Do we want the boring old get*() form for our properties? Since we added @Accessors(fluent=true) in this example, “getters” would have the same method name as the properties; getAuthToken() simply becomes authToken().

我们是否想让我们的属性采用无聊的老式get*()形式?由于我们在这个例子中添加了@Accessors(fluent=true),”getters “将拥有与属性相同的方法名; getAuthToken()简单地变成authToken()

This “fluent” form would apply to non-final fields for attribute setters, as well as allow for chained calls:

这种 “流畅 “的形式将适用于属性设置器的非最终字段,并允许链式调用。

// Imagine fields were no longer final now
return new LoginResult()
  .loginTs(Instant.now())
  .authToken("asdasd")
  . // and so on

5. Core Java Boilerplate

5.核心Java模板</strong

Another situation where we end up writing code we need to maintain is when generating the toString(), equals() and hashCode() methods. IDEs try to help with templates for auto-generating these in terms of our class attributes.

另一种情况是,在生成toString()equals()hashCode()方法时,我们最终会写下需要维护的代码。IDE试图用模板来帮助我们自动生成这些类的属性。

We can automate this by means of other Lombok class-level annotations:

我们可以通过其他Lombok类级注释来实现自动化。

  • @ToString: will generate a toString() method including all class attributes. No need to write one ourselves and maintain it as we enrich our data model.
  • @EqualsAndHashCode: will generate both equals() and hashCode() methods by default considering all relevant fields, and according to very well though semantics.

These generators ship very handy configuration options. For example, if our annotated classes take part of a hierarchy, we can just use the callSuper=true parameter and parent results will be considered when generating the method’s code.

这些生成器提供了非常方便的配置选项。例如,如果我们的注释类属于一个层次结构,我们只需使用callSuper=true参数,在生成方法的代码时将考虑父级结果。

To demonstrate this, let’s say we had our User JPA entity example include a reference to events associated to this user:

为了证明这一点,让我们假设我们的UserJPA实体例子包括对与该用户相关的事件的引用。

@OneToMany(mappedBy = "user")
private List<UserEvent> events;

We wouldn’t want to have the whole list of events dumped whenever we call the toString() method of our User, just because we used the @ToString annotation. Instead, we can parameterize it like this, @ToString(exclude = {“events”}), and that won’t happen. This is also helpful to avoid circular references if, for example, UserEvents had a reference to a User.

我们不想因为使用了@ToString注解,而在我们调用用户的toString()方法时,整个事件列表被转储。相反,我们可以像这样将其参数化, @ToString(exclude = {“events”}),这样就不会发生。这也有助于避免循环引用,例如,如果UserEvents有一个对User的引用。

For the LoginResult example, we may want to define equality and hash code calculation just in terms of the token itself and not the other final attributes in our class. Then we can simply write something like @EqualsAndHashCode(of = {“authToken”}).

对于LoginResult的例子,我们可能想只用令牌本身来定义平等和哈希代码计算,而不是用我们类中的其他最终属性。那么我们可以简单地写一些东西,如@EqualsAndHashCode(of = {“authToken”})

If the features from the annotations we’ve reviewed so far are of interest, we may want to examine @Data and @Value annotations too, as they behave as if a set of them had been applied to our classes. After all, these discussed usages are very commonly put together in many cases.

如果我们到目前为止所回顾的注解中的特征是我们感兴趣的,我们可能想检查一下@Data@Value注解,因为它们的行为就像一组它们被应用到我们的类中。毕竟,这些讨论过的用法在很多情况下都是非常普遍地放在一起的。

5.1. (Not) Using the @EqualsAndHashCode With JPA Entities

5.1. (不)使用@EqualsAndHashCode与JPA实体

Whether we should use the default equals() and hashCode() methods, or create custom ones for the JPA entities, is an often discussed topic among developers. There are multiple approaches we can follow, each having its pros and cons.

我们是应该使用默认的equals()hashCode()方法,还是为JPA实体创建自定义的方法,这是开发人员之间经常讨论的话题。我们可以采用多种方法,每种方法都有其优点和缺点。

By default, @EqualsAndHashCode includes all non-final properties of the entity class. We can try to “fix” this by using the onlyExplicitlyIncluded attribute of the @EqualsAndHashCode to make Lombok use only the entity’s primary key. Still, the generated equals() method can cause some issues. Thorben Janssen explains this scenario in greater detail in one of his blog posts.

默认情况下,@EqualsAndHashCode包括实体类的所有非最终属性。我们可以尝试通过使用@EqualsAndHashCodeonlyExplicitlyIncluded属性来 “解决 “这个问题,使Lombok只使用实体的主键。不过,生成的equals()方法还是会引起一些问题。Thorben Janssen在他的一篇博文中更详细地解释了这种情况。

In general, we should avoid using Lombok to generate the equals() and hashCode() methods for our JPA entities.

一般来说,我们应该避免使用Lombok为我们的JPA实体生成equals()/em>和hashCode()/em>方法。

6. The Builder Pattern

6.建设者模式

The following could make for a sample configuration class for a REST API client:

下面的内容可以作为REST API客户端的一个配置类样本。

public class ApiClientConfiguration {

    private String host;
    private int port;
    private boolean useHttps;

    private long connectTimeout;
    private long readTimeout;

    private String username;
    private String password;

    // Whatever other options you may thing.

    // Empty constructor? All combinations?

    // getters... and setters?
}

We could have an initial approach based on using the class default empty constructor and providing setter methods for every field; however, we ideally want configurations not to be re-set once they’ve been built (instantiated), effectively making them immutable. Therefore, we want to avoid setters, but writing such a potentially long args constructor is an anti-pattern.

我们可以有一个基于使用类的默认空构造函数的初始方法,并为每个字段提供设置方法;然而,我们理想地希望配置一旦被构建(实例化)就不会被重新设置,有效地使它们变得不可改变。因此,我们希望避免设置器,但编写这样一个潜在的长args构造函数是一种反模式。

Instead, we can tell the tool to generate a builder pattern, which negates us from having to write an extra Builder class and the associated fluent setter-like methods by simply adding the @Builder annotation to our ApiClientConfiguration:

相反,我们可以告诉该工具生成一个builder模式,这使我们不必再编写一个额外的Builder类和相关的类似于流畅的setter的方法,只需在我们的ApiClientConfiguration>中添加@Builder注解:

@Builder
public class ApiClientConfiguration {

    // ... everything else remains the same

}

Leaving the class definition such as above (no declare constructors or setters + @Builder), we can end up using it as:

离开了上面这样的类定义(没有声明构造函数或设置函数+@Builder),我们最终可以把它作为。

ApiClientConfiguration config = 
    ApiClientConfiguration.builder()
        .host("api.server.com")
        .port(443)
        .useHttps(true)
        .connectTimeout(15_000L)
        .readTimeout(5_000L)
        .username("myusername")
        .password("secret")
    .build();

7. Checked Exceptions Burden

7.检查过的异常情况的负担

Lots of Java APIs are designed so that they can throw a number of checked exceptions; client code is forced to either catch or declare to throws. How many times have we turned these exceptions we know won’t happen into something like this?:

很多Java API被设计成可以抛出一些被检查的异常;客户端代码被迫要么catch,要么声明到throws。我们有多少次把这些我们知道不会发生的异常变成了这样的东西?

public String resourceAsString() {
    try (InputStream is = this.getClass().getResourceAsStream("sure_in_my_jar.txt")) {
        BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
        return br.lines().collect(Collectors.joining("\n"));
    } catch (IOException | UnsupportedCharsetException ex) {
        // If this ever happens, then its a bug.
        throw new RuntimeException(ex); <--- encapsulate into a Runtime ex.
    }
}

If we want to avoid this code pattern because the compiler won’t be happy (and we know the checked errors cannot happen), use the aptly named @SneakyThrows:

如果我们想避免这种代码模式,因为编译器不会高兴(而且我们知道检查过的错误不可能发生),可以使用恰如其分的@SneakyThrows

@SneakyThrows
public String resourceAsString() {
    try (InputStream is = this.getClass().getResourceAsStream("sure_in_my_jar.txt")) {
        BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
        return br.lines().collect(Collectors.joining("\n"));
    } 
}

8. Ensure Our Resources Are Released

8.确保我们的资源得到释放

Java 7 introduced the try-with-resources block to ensure that our resources being held by instances of anything implementing java.lang.AutoCloseable are released when exiting.

Java 7 引入了 try-with-resources 块,以确保我们被任何实现 java.lang.AutoCloseable 的实例所持有的资源在退出时被释放。

Lombok provides an alternative and more flexible way of achieving this via @Cleanup. We can use it for any local variable whose resources we want to make sure are released. There’s no need for them to implement any particular interface, we’ll just get the close() method called:

Lombok通过@Cleanup提供了另一种更灵活的方式来实现这一目标。我们可以将其用于任何我们想要确保其资源被释放的局部变量。没有必要让它们实现任何特定的接口,我们只需要让close()方法被调用。

@Cleanup InputStream is = this.getClass().getResourceAsStream("res.txt");

Our releasing method has a different name? No problem, we just customize the annotation:

我们的释放方法有一个不同的名字?没问题,我们只需定制注释。

@Cleanup("dispose") JFrame mainFrame = new JFrame("Main Window");

9. Annotate Our Class to Get a Logger

9.注释我们的类以获得一个记录器

Many of us add logging statements to our code sparingly by creating an instance of a Logger from our framework of choice. Let’s say SLF4J:

我们中的许多人通过从我们选择的框架中创建一个Logger的实例来为我们的代码添加日志语句。比方说SLF4J。

public class ApiClientConfiguration {

    private static Logger LOG = LoggerFactory.getLogger(ApiClientConfiguration.class);

    // LOG.debug(), LOG.info(), ...

}

This is such a common pattern that Lombok developers have simplified it for us:

这是一个很常见的模式,Lombok开发者为我们简化了它。

@Slf4j // or: @Log @CommonsLog @Log4j @Log4j2 @XSlf4j
public class ApiClientConfiguration {

    // log.debug(), log.info(), ...

}

Many logging frameworks are supported, and of course we can customize the instance name, topic, etc.

支持许多日志框架,当然我们也可以自定义实例名称、主题等。

10. Write Thread-Safer Methods

10.编写线程安全的方法

In Java, we can use the synchronized keyword to implement critical sections; however, this is not a 100% safe approach. Other client code can eventually also synchronize on our instance, potentially leading to unexpected deadlocks.

在Java中,我们可以使用synchronized关键字来实现关键部分;然而,这并不是一个100%安全的方法。其他客户端代码最终也可以在我们的实例上进行同步,可能会导致意外的死锁。

This is where @Synchronized comes in. We can annotate our methods (both instance and static) with it, and we’ll get an auto-generated, private, unexposed field that our implementation will use for locking:

这就是@Synchronized 的用处。我们可以用它来注解我们的方法(包括实例和静态方法),我们将得到一个自动生成的、私有的、未暴露的字段,我们的实现将使用它来锁定。

@Synchronized
public /* better than: synchronized */ void putValueInCache(String key, Object value) {
    // whatever here will be thread-safe code
}

11. Automate Objects Composition

11.自动化对象构成

Java doesn’t have language level constructs to smooth out a “favor composition inheritance” approach. Other languages have built-in concepts such as Traits or Mixins to achieve this.

Java没有语言层面的构造来平滑 “有利于组成继承 “的方法。其他语言有内置的概念,如TraitsMixins来实现这一点。

Lombok’s @Delegate comes in very handy when we want to use this programming pattern. Let’s consider an example:

当我们想使用这种编程模式时,Lombok的@Delegate就会非常方便。让我们考虑一个例子。

  • We want Users and Customers to share some common attributes for naming and phone number.
  • We define both an interface and an adapter class for these fields.
  • We’ll have our models implement the interface and @Delegate to their adapter, effectively composing them with our contact information.

First, let’s define an interface:

首先,让我们定义一个接口。

public interface HasContactInformation {

    String getFirstName();
    void setFirstName(String firstName);

    String getFullName();

    String getLastName();
    void setLastName(String lastName);

    String getPhoneNr();
    void setPhoneNr(String phoneNr);

}

Now an adapter as a support class:

现在适配器作为一个支持类。

@Data
public class ContactInformationSupport implements HasContactInformation {

    private String firstName;
    private String lastName;
    private String phoneNr;

    @Override
    public String getFullName() {
        return getFirstName() + " " + getLastName();
    }
}

Now for the interesting part; see how easy it is to compose contact information into both model classes:

现在是有趣的部分;看看将联系信息编入两个模型类是多么容易。

public class User implements HasContactInformation {

    // Whichever other User-specific attributes

    @Delegate(types = {HasContactInformation.class})
    private final ContactInformationSupport contactInformation =
            new ContactInformationSupport();

    // User itself will implement all contact information by delegation
    
}

The case for Customer would be so similar that we can omit the sample for brevity.

客户的情况非常相似,为了简洁起见,我们可以省略这个样本。

12. Rolling Lombok Back?

12.龙目岛卷土重来?

Short answer: Not at all really.

简短的回答。其实一点也不。

There may be a worry that if we use Lombok in one of our projects, we may later want to rollback that decision. The potential problem could be that there are a large number of classes annotated for it. In this case, we’re covered thanks to the delombok tool from the same project.

我们可能会担心,如果我们在某个项目中使用Lombok,我们以后可能会想收回这个决定。潜在的问题可能是,有大量的类为它做了注释。在这种情况下,由于同一项目中的delombok工具,我们可以得到保障。

By delombok-ing our code, we get auto-generated Java source code with exactly the same features from the bytecode Lombok built. We can then simply replace our original annotated code with these new delomboked files, and no longer depend on it.

通过delombok-我们的代码,我们得到了自动生成的Java源代码,其特征与Lombok构建的字节码完全相同。然后我们可以简单地用这些新的delomboked文件替换我们原来的注释代码,而不再依赖它。

This is something we can integrate in our build.

这是我们可以在我们的构建中整合的东西

13. Conclusion

13.结论

There are still some other features we haven’t presented in this article. We can take a deeper dive into the feature overview for more details and use cases.

还有一些其他的功能我们没有在这篇文章中介绍。我们可以深入了解功能概述,以了解更多细节和使用案例。

Furthermore, most functions we’ve shown have a number of customization options that we may find handy. The available built-in configuration system could also help us with that.

此外,我们所展示的大多数功能都有一些自定义选项,我们可能会发现它们很方便。可用的内置配置系统也可以帮助我们做到这一点。

Now that we can give Lombok a chance to get into our Java development tool set, we can boost our productivity.

现在我们可以给Lombok一个机会进入我们的Java开发工具集,我们可以提高我们的生产力。

The example code can be found in the GitHub project.

示例代码可以在GitHub项目中找到。