详解Lombok中的@Builder及@SuperBuilder用法

本篇文章详细介绍了Java中lombok的@Builder注解及@SuperBuilder注解的解析和使用,希望对大家的学习或工作有一定的参考借鉴价值。废话不多说,直接上干货。

@Builder

Lombok 的@Builder提供了一种非常有用的机制,无需编写样板代码即可使用构建者模式。 @Builder可以放在类,构造函数或方法上。

先定义示例类 Question,类声明中用 @Builder 注解。

// 定义 Quesstion, 使用@Builder注解
@Builder
public class Question {
    private Long id;
    private String question;
}

@Builder 注解的类,Lombok会帮我们做以下几个事情(参考下面的示例代码):

public class Question {
    private Long id;
    private String question;

    Question(Long id, String question) {
        this.id = id;
        this.question = question;
    }

    public static QuestionBuilder builder() {
        return new QuestionBuilder();
    }

    public static class QuestionBuilder {
        private Long id;
        private String question;

        QuestionBuilder() {
        }

        public QuestionBuilder id(Long id) {
            this.id = id;
            return this;
        }

        public QuestionBuilder question(String question) {
            this.question = question;
            return this;
        }

        public Question build() {
            return new Question(this.id, this.question);
        }

        public String toString() {
            return "Question.QuestionBuilder(id=" + this.id + ", question=" + this.question + ")";
        }
    }

@Builder中使用 @Singular 注释集合。

// 定义 Answer,使用 @Builder注解
@Builder
public class Answer {
    private Long id;
    private String answer;
    
}

// 定义 Quesstion, 使用@Builder注解
@Builder
public class Question {
    private Long id;
    private String question;

    @Singular
    private List answers;

    // @Singular("answer")
    // private List answerList;
}

在使用@Singular注释注释一个集合字段(使用@Builder注释类),lombok会将该构建器节点视为一个集合,并生成两个adder方法,而不是setter方法。

除此之外,还生成了clear方法,用于清空集合。

public class Question {
    private Long id;
    private String question;
    List answers;

    Question(Long id, String question, List answers) {
        this.id = id;
        this.question = question;
        this.answers = answers;
    }

    public static QuestionBuilder builder() {
        return new QuestionBuilder();
    }

    public static class QuestionBuilder {
        private Long id;
        private String question;
        private ArrayList answers;

        QuestionBuilder() {
        }

        public QuestionBuilder id(Long id) {
            this.id = id;
            return this;
        }

        public QuestionBuilder question(String question) {
            this.question = question;
            return this;
        }

        public QuestionBuilder answer(Answer answer) {
            if (this.answers == null) {
                this.answers = new ArrayList();
            }

            this.answers.add(answer);
            return this;
        }

        public QuestionBuilder answers(Collection<? extends Answer> answers) {
            if (answers == null) {
                throw new NullPointerException("answers cannot be null");
            } else {
                if (this.answers == null) {
                    this.answers = new ArrayList();
                }

                this.answers.addAll(answers);
                return this;
            }
        }

        public QuestionBuilder clearAnswers() {
            if (this.answers != null) {
                this.answers.clear();
            }

            return this;
        }

        public Question build() {
            List answers;
            switch (this.answers == null ? 0 : this.answers.size()) {
                case 0:
                    answers = Collections.emptyList();
                    break;
                case 1:
                    answers = Collections.singletonList(this.answers.get(0));
                    break;
                default:
                    answers = Collections.unmodifiableList(new ArrayList(this.answers));
            }

            return new Question(this.id, this.question, answers);
        }

        public String toString() {
            return "Question.QuestionBuilder(id=" + this.id + ", question=" + this.question + ", answers=" + this.answers + ")";
        }
    }
}

从上面代码块,可以看到 在集合字段增加了@Singular注解后,构建器的 build() 方法会更复杂一些,主要是为了保证以下两点:

如果您的标识符是用普通英语编写的,lombok 会假定任何带有 @Singular 的集合的名称是英语复数,并将尝试自动将该名称单数化。 如果可能,add-one 方法将使用此名称。 例如,如果这里我们定义的集合为answers,那么 add-one 方法将自动称为 answer(Answer answer)。 您还可以在@Singular注解中显式指定标识符的单数形式,如上面代码块中被注释的部分:@Singular("answer") private List answerList;

如果 lombok 无法将您的标识符单数化,或者它有歧义,lombok 将生成错误并强制您明确指定单数名称。

@Builder.Default 的使用

如果在构建会话期间从未设置某个字段/参数,则它始终为 0/null/false。 如果您将 @Builder 放在类上(而不是方法或构造函数),您可以直接在字段上指定默认值,并使用 @Builder.Default 注释该字段:

@Builder
public class Answer {
    @Builder.Default
    private final String id = UUID.randomUUID().toString();
    private String answer;
}

@Builder(toBuilder=true)

如果我们想要创建对象的副本或近似副本,我们可以将属性 toBuilder = true 添加到 @Builder 注释中:

Lombok 会在目标类中新增一个 toBuilder() 方法。当调用 toBuilder() 方法时,它会返回一个新的构建器,该构建器使用调用它的实例的属性进行初始化:

public class Answer {
    private final String id;
    private String answer;

    private static String $default$id() {
        return UUID.randomUUID().toString();
    }

    Answer(String id, String answer) {
        this.id = id;
        this.answer = answer;
    }

    public static AnswerBuilder builder() {
        return new AnswerBuilder();
    }

    public AnswerBuilder toBuilder() {
        return (new AnswerBuilder()).id(this.id).answer(this.answer);
    }

    public static class AnswerBuilder {
        private boolean id$set;
        private String id$value;
        private String answer;

        AnswerBuilder() {
        }

        public AnswerBuilder id(String id) {
            this.id$value = id;
            this.id$set = true;
            return this;
        }

        public AnswerBuilder answer(String answer) {
            this.answer = answer;
            return this;
        }

        public Answer build() {
            String id$value = this.id$value;
            if (!this.id$set) {
                id$value = Answer.$default$id();
            }

            return new Answer(id$value, this.answer);
        }

        public String toString() {
            return "Answer.AnswerBuilder(id$value=" + this.id$value + ", answer=" + this.answer + ")";
        }
    }
}

@SuperBuilder

@Builder 并不支持对父类成员属性的构造,为解决这个问题, @SuperBuilder 应运而生,算是 @Builder 的升级版。@SuperBuilder在 lombok v1.18.2 中作为实验性功能引入。

定义示例类 Event和其子类 QuestionEvent。

@SuperBuilder
public class Event {
    String message;
}

@SuperBuilder
public class QuestionEvent extends Event {
}

用@SuperBuilder 注解的类,Lombok会帮我们做以下几个事情(参考下面的示例代码):

public class Event {
    String message;

    protected Event(EventBuilder<?, ?> b) {
        this.message = b.message;
    }

    public static EventBuilder<?, ?> builder() {
        return new EventBuilderImpl();
    }

    private static final class EventBuilderImpl extends EventBuilder {
        private EventBuilderImpl() {
        }

        protected EventBuilderImpl self() {
            return this;
        }

        public Event build() {
            return new Event(this);
        }
    }

    public abstract static class EventBuilder> {
        private String message;

        public EventBuilder() {
        }

        protected abstract B self();

        public abstract C build();

        public B message(String message) {
            this.message = message;
            return this.self();
        }

        public String toString() {
            return "Event.EventBuilder(message=" + this.message + ")";
        }
    }
}
public class QuestionEvent extends Event {
    protected QuestionEvent(QuestionEventBuilder<?, ?> b) {
        super(b);
    }

    public static QuestionEventBuilder<?, ?> builder() {
        return new QuestionEventBuilderImpl();
    }

    private static final class QuestionEventBuilderImpl extends QuestionEventBuilder {
        private QuestionEventBuilderImpl() {
        }

        protected QuestionEventBuilderImpl self() {
            return this;
        }

        public QuestionEvent build() {
            return new QuestionEvent(this);
        }
    }

    public abstract static class QuestionEventBuilder> extends Event.EventBuilder {
        public QuestionEventBuilder() {
        }

        protected abstract B self();

        public abstract C build();

        public String toString() {
            return "QuestionEvent.QuestionEventBuilder(super=" + super.toString() + ")";
        }
    }
}

原因在于,在Java的抽象语法树设计上,每个类只包含了显式声明的变量而不包括父类的成员变量。Lombok针对@Builder注解的内部实现findAllFields方法是从当前类的抽象语法树出发去找所有的成员变量,所以就只能找到当前类的成员变量,而访问不到父类的成员变量。

@SuperBuilder注解的内部实现,在查找所有成员变量之前,先拿到了继承的父类的抽象语法树。

JCClassDecl td = (JCClassDecl) parent.get();
// 获取继承的父类的抽象语法树
JCTree extendsClause = Javac.getExtendsClause(td);
JCExpression superclassBuilderClass = null;
if (extendsClause instanceof JCTypeApply) {
   // Remember the type arguments, because we need them for the extends clause of our abstract builder class.
   superclassTypeParams = ((JCTypeApply) extendsClause).getTypeArguments();
   // A class name with a generics type, e.g., "Superclass".
   extendsClause = ((JCTypeApply) extendsClause).getType();
}
if (extendsClause instanceof JCFieldAccess) {
   Name superclassName = ((JCFieldAccess) extendsClause).getIdentifier();
   String superclassBuilderClassName = superclassName.toString() + "Builder";
   superclassBuilderClass = parent.getTreeMaker().Select((JCFieldAccess) extendsClause, parent.toName(superclassBuilderClassName));
} else if (extendsClause != null) {
   String superclassBuilderClassName = extendsClause.toString() + "Builder";
   superclassBuilderClass = chainDots(parent, extendsClause.toString(), superclassBuilderClassName);
}

注意点

  1. @SuperBuilder 与@Builder 不兼容,不能一起使用。
  2. 被 @SuperBuilder 注解的类,其父类也必须使用@SuperBuilder注解。
展开阅读全文

页面更新:2024-03-16

标签:单数   注解   字段   注释   变量   详解   定义   成员   名称   目标   方法

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号

Top