Lombok @Builder with Inheritance – Lombok @Builder的继承性

最后修改: 2018年 9月 12日

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

1. Overview

1.概述

The Lombok library provides a great way to implement the Builder Pattern without writing any boilerplate code: the @Builder annotation.

Lombok库提供了一个实现Builder模式的好方法,无需编写任何模板代码:@Builder注释。

In this short tutorial, we’re specifically going to learn how to deal with the @Builder annotation when inheritance is involved. We will demonstrate two techniques. One relies on standard Lombok features. The other makes use of an experimental feature introduced in Lombok 1.18.

在这个简短的教程中,我们将专门学习当涉及到继承时如何处理@Builder注解。我们将演示两种技术。一个是依靠标准的Lombok功能。另一个是利用Lombok 1.18中引入的一个实验性功能。

For a wider overview of the Builder annotation, refer to Using Lombok’s @Builder Annotation.

关于Builder注释的更广泛的概述,请参阅使用Lombok的@Builder注释

A detailed look at the Project Lombok library is also available in Introduction to Project Lombok.

Project Lombok简介中也有对Project Lombok库的详细介绍。

2. Lombok @Builder and Inheritance

2.Lombok@Builder和继承

2.1. Defining the Problem

2.1.界定问题

Let’s suppose our Child class extends a Parent class:

我们假设我们的Child类扩展了一个Parent类。

@Getter
@AllArgsConstructor
public class Parent {
    private final String parentName;
    private final int parentAge;
}

@Getter
@Builder
public class Child extends Parent {
    private final String childName;
    private final int childAge;
}

When using @Builder on a class that extends another class like that, we’ll get the following compilation error on the annotation:

当在一个像这样扩展另一个类的类上使用@Builder时,我们会在注解上得到如下编译错误。

Implicit super constructor Parent() is undefined. Must explicitly invoke another constructor

隐式超级构造函数Parent()是未定义的。必须明确地调用另一个构造函数

This is due to the fact that Lombok doesn’t take into account the fields of the superclasses but only the ones from the current class.

这是因为Lombok不考虑超类的字段,只考虑当前类的字段。

2.2. Solving the Problem

2.2.解决问题

Luckily for us, there’s a simple workaround. We can generate (with our IDE or even manually) a field-based constructor. This also includes the fields from the superclasses.

幸运的是,我们有一个简单的解决方法。我们可以(用我们的IDE或甚至手动)生成一个基于字段的构造函数。这也包括来自超类的字段。

We annotate it with @Builder, instead of the class:

我们用@Builder来注解它,而不是用类。

@Getter
@AllArgsConstructor
public class Parent {
    private final String parentName;
    private final int parentAge;
}

@Getter
public class Child extends Parent {
    private final String childName;
    private final int childAge;

    @Builder
    public Child(String parentName, int parentAge, String childName, int childAge) {
        super(parentName, parentAge);
        this.childName = childName;
        this.childAge = childAge;
    }
}

This way, we’ll be able to access a convenient builder from the Child class, which will allow us to also specify the Parent class fields:

这样,我们就能从Child类中访问一个方便的构建器,这将使我们也能指定Parent类的字段。

Child child = Child.builder()
  .parentName("Andrea")
  .parentAge(38)
  .childName("Emma")
  .childAge(6)
  .build();

assertThat(child.getParentName()).isEqualTo("Andrea");
assertThat(child.getParentAge()).isEqualTo(38);
assertThat(child.getChildName()).isEqualTo("Emma");
assertThat(child.getChildAge()).isEqualTo(6);

2.3. Making Multiple @Builders Coexist

2.3.使多个@Builders共存

In case the superclass itself is annotated with @Builder, we’ll get the following error when annotating the Child class constructor:

如果超类本身是用@Builder注释的,那么在注释Child类构造函数时,我们会得到如下错误。

The return type is incompatible with Parent.builder()

返回类型与Parent.builder()不兼容。

This is because the Child class is trying to expose both the Builders with the same name.

这是因为Child类正试图以相同的名字暴露两个Builders。

We can fix this problem by assigning a unique name to at least one of the builder methods:

我们可以通过为至少一个构建器方法分配一个独特的名字来解决这个问题。

@Getter
public class Child extends Parent {
    private final String childName;
    private final int childAge;
    
    @Builder(builderMethodName = "childBuilder")
    public Child(String parentName, int parentAge, String childName, int childAge) {
        super(parentName, parentAge);
        this.childName = childName;
        this.childAge = childAge;
    }
}

We’ll then be able to obtain a ParentBuilder through Child.builder() and a ChildBuilder through Child.childBuilder().

然后我们就可以通过Child.builder()获得一个ParentBuilder,通过Child.childBuilder()获得一个ChildBuilder

2.4. Supporting Larger Inheritance Hierarchies

2.4.支持更大的继承层次结构

In some cases, we may need to support deeper inheritance hierarchies. We can make use of the same pattern as before.

在某些情况下,我们可能需要支持更深的继承层次。我们可以利用与之前相同的模式。

Let’s create a subclass of Child:

让我们创建一个Child的子类。

@Getter
public class Student extends Child {

    private final String schoolName;

    @Builder(builderMethodName = "studentBuilder")
    public Student(String parentName, int parentAge, String childName, int childAge, String schoolName) {
        super(parentName, parentAge, childName, childAge);
        this.schoolName = schoolName;
    }
}

As before, we need to manually add a constructor. This needs to accept all properties from all the parent classes, and the child, as arguments. We then add the @Builder annotation as before.

和以前一样,我们需要手动添加一个构造函数。这需要接受所有父类和子类的所有属性作为参数。然后我们像以前一样添加@Builder注解。

By providing another unique method name in the annotation, we can obtain builders for Parent, Child or Student:

通过在注释中提供另一个独特的方法名,我们可以获得ParentChildStudent的构建器。

Student student = Student.studentBuilder()
  .parentName("Andrea")
  .parentAge(38)
  .childName("Emma")
  .childAge(6)
  .schoolName("Baeldung High School")
  .build();

assertThat(student.getChildName()).isEqualTo("Emma");
assertThat(student.getChildAge()).isEqualTo(6);
assertThat(student.getParentName()).isEqualTo("Andrea");
assertThat(student.getParentAge()).isEqualTo(38);
assertThat(student.getSchoolName()).isEqualTo("Baeldung High School");

We can then extend this pattern to deal with any depth of inheritance. The constructor that we need to create can become quite large, but our IDE can help us out.

然后我们可以扩展这种模式,以处理任何深度的继承。我们需要创建的构造函数可能会变得相当大,但我们的IDE可以帮助我们解决。

3. Lombok @SuperBuilder and Inheritance

3.Lombok@SuperBuilder和继承

As we noted earlier, version 1.18 of Lombok introduced the @SuperBuilder annotation. We can use this to solve our problem in a simpler way.

正如我们前面指出的,Lombok的1.18版引入了@SuperBuilder注解。我们可以用它来以更简单的方式解决我们的问题。

3.1. Applying the Annotations

3.1.应用注释

We can make a builder that can see the properties of its ancestors.

我们可以制作一个可以看到其祖先属性的构建器。

To do this, we annotate our class and its ancestors with the @SuperBuilder annotation.

为了做到这一点,我们用@SuperBuilder注解来注解我们的类和它的祖先。

Let’s demonstrate on our three-tier hierarchy here.

让我们在这里演示一下我们的三层层次结构。

Note that the principle for simple parent and child inheritance is the same:

请注意,简单的父继承和子继承的原理是一样的。

@Getter
@SuperBuilder
public class Parent {
    // same as before...

@Getter
@SuperBuilder
public class Child extends Parent {
   // same as before...

@Getter
@SuperBuilder
public class Student extends Child {
   // same as before...

When all classes are annotated in this way, we get a builder for the child class that exposes the properties of the parents too.

当所有的类都以这种方式被注解时,我们就会得到一个子类的构建器,它也暴露了父类的属性。

Note that we have to annotate all classes. @SuperBuilder cannot be mixed with @Builder within the same class hierarchy. Doing so will result in a compilation error.

请注意,我们必须对所有的类进行注解。@SuperBuilder不能与@Builder在同一个类的层次结构中混合。这样做将导致编译错误。

3.2. Using the Builder

3.2.使用生成器

This time, we don’t need to define any special constructors.

这一次,我们不需要定义任何特殊的构造函数。

The builder class generated by @SuperBuilder behaves just like the one we generated using the main Lombok @Builder:

@SuperBuilder生成的构建器类的行为与我们使用主Lombok @Builder生成的构建器类的行为一致。

Student student = Student.builder()
  .parentName("Andrea")
  .parentAge(38)
  .childName("Emma")
  .childAge(6)
  .schoolName("Baeldung High School")
  .build();

assertThat(student.getChildName()).isEqualTo("Emma");
assertThat(student.getChildAge()).isEqualTo(6);
assertThat(student.getParentName()).isEqualTo("Andrea");
assertThat(student.getParentAge()).isEqualTo(38);
assertThat(student.getSchoolName()).isEqualTo("Baeldung High School");

4. Conclusion

4.结论

We’ve seen how to deal with the common pitfalls of using the @Builder annotation in classes that make use of inheritance.

我们已经看到了如何处理在利用继承的类中使用@Builder注解的常见陷阱。

If we use the main Lombok @Builder annotation, we have a few extra steps to make it work. But if we are willing to use the experimental features, @SuperBuilder can simplify things.

如果我们使用Lombok的主@Builder注解,我们有一些额外的步骤来使其工作。但是如果我们愿意使用实验性的功能,@SuperBuilder可以简化事情。

As always, the full source code is available over on GitHub.

一如既往,完整的源代码可在GitHub上获得