如何在Lombok构建器中排除属性?

124

我有一个名为"XYZClientWrapper"的类,其结构如下:

@Builder
XYZClientWrapper{
    String name;
    String domain;
    XYZClient client;
}

我希望不生成属性 XYZClient client 的构建函数。

Lombok支持这种用例吗?

13个回答

218

是的,您可以在构造函数或静态(工厂)方法中放置@Builder,其中只包含您想要的字段。

声明:我是Lombok开发人员。


80
我发现这个问题和答案,因为我也有同样的问题在脑海中。在我的情况下,构造函数解决方案不是一个好的选择。我有一个包含29个属性(实体)的类,只想让其中一个属性不被包含在生成器中(事实上:id)。是否有一种像@ToString中的排除机制呢?使用Lombok的主要原因是避免混乱。我不想看到有28个参数的构造函数。 - Staratnight
55
有没有计划在@Builder注释中添加类似于“_ignore_”属性或将注释@Builder.Ignore添加到字段中? - pirho
11
如果字段已经被初始化为默认值,则不起作用。您无法重新分配最终字段。在我看来,在“构建器”上提供一个“忽略”属性只是常识。 - Abhijit Sarkar
5
由于您明确表示您是Lombock开发者,所以我对您鼓励创建构造函数感到困惑,因为这正是生成器试图避免的。我不知道您是否阅读了@pirho的问题,但它似乎是该库的下一个自然步骤,这将非常有帮助。 - Carlos López Marí
5
有趣。Lombok提供了避免样板式代码的方法。现在它建议我们去做它曾经让我们避免的事情。 - Jin Kwon
显示剩余11条评论

42

另外,我发现将字段标记为finalstaticstatic final会指示@Builder忽略此字段。

@Builder
public class MyClass {
   private String myField;

   private final String excludeThisField = "bar";
}

Lombok 1.16.10


2
感谢Stephan的回答。在我的情况下,它不起作用,因为我想要忽略的字段是从父类派生的。 - Staratnight
8
什么?!我每天都使用带有final字段的构建器,它们怎么可能被忽略? :D 目前使用的版本是1.16.12。 - Dominik
3
@Stephan 这怎么可能呢?我的意思是:构建器模式的目的是避免具有所有这些冗长参数列表的构造函数。字段为final与此无关:构建器的目的是以“链式方法风格”收集所有参数,而不是一个巨大的块。 另外,我已经检查过,添加final不会影响构建器的构建。 - Piohen
8
太好了!@Builder只会添加没有值的final字段:final String myField - 存在, final String myField="" - 被忽略。 :) - Cherry
2
记住,并非所有内容都是线程安全的,因此将其设置为“public static”并不总是最好的选择。此外,仅以这种方式初始化属性仅适用于简单情况,而不适用于需要考虑其他字段值的情况。 - Abhijit Sarkar
显示剩余4条评论

29
在代码中创建构建器并为您的属性添加私有设置器。
@Builder
XYZClientWrapper{
    String name;
    String domain;
    XYZClient client;

    public static class XYZClientWrapperBuilder {
        private XYZClientWrapperBuilder client(XYZClient client) { return this; }
    }
}

3
client参数会被忽略。 - Abhijit Sarkar
6
目前为止最佳解决方案。 - Carlos López Marí
甚至可以与 private void client() {} 一起使用。 - David

17
这是我的首选解决方案。通过这个,你可以在最后创建你的字段client,并且它可以依赖于先前由构建器设置的其他字段。
XYZClientWrapper{
    private final String name;
    private final String domain;
    private final XYZClient client;
    
    @Builder
    public XYZClientWrapper(String name, String domain) {
        this.name = name;
        this.domain = domain;
        this.client = calculateClient();
    }
}

1
这违背了构建器的设计原则。字段不再是私有或最终的。如果您想要默认值,您必须自己设置。 - Abhijit Sarkar
2
你可以将它们设为最终和私有的。 - TriCore

9

针对工厂静态方法的示例:

class Car {
   private String name;
   private String model;


   private Engine engine; // we want to ignore setting this
   
   @Builder
   private static Car of(String name, String model){
      Car car=new Car();
      car.name = name;
      car.model = model;
      constructEngine(car); // some static private method to construct engine internally
      return car;  
   }

   private static void constructEngine(Car car) {
       // car.engine = blabla...
       // construct engine internally
   }
}

然后你可以按照以下方式使用:
Car toyotaCorollaCar=Car.builder().name("Toyota").model("Corolla").build();
// You can see now that Car.builder().engine() is not available

注意,静态方法of将在调用build()时被调用,因此执行Car.builder().name("Toyota")这样的操作并不会将值"Toyota"设置到name中,除非调用了build(),然后在构造函数的静态方法of内执行赋值逻辑。
另外,请注意of方法是私有访问的,以便build方法是唯一对调用方可见的方法。

6
在Lombok的@Builder类中添加一个所谓的“partial builder”可以帮助解决问题。诀窍是像这样添加一个内部的局部构建器类:
@Getter
@Builder
class Human {
    private final String name;
    private final String surname;
    private final Gender gender;
    private final String prefix; // Should be hidden, depends on gender

    // Partial builder to manage dependent fields, and hidden fields
    public static class HumanBuilder {

        public HumanBuilder gender(final Gender gender) {
            this.gender = gender;
            if (Gender.MALE == gender) {
                this.prefix = "Mr.";
            } else if (Gender.FEMALE == gender) {
                this.prefix = "Ms.";
            } else {
                this.prefix = "";
            }
            return this;
        }

        // This method hides the field from external set 
        private HumanBuilder prefix(final String prefix) {
            return this;
        }

    }

}

PS:@Builder允许更改生成的构建器类名称。上面的示例假定使用默认的构建器类名称。


5

我发现我可以实现静态Builder类的"外壳",使用私有访问修饰符添加我想要隐藏的方法,并且它在构建器中不再可用。同样,我也可以向构建器添加自定义方法。

package com.something;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import java.time.ZonedDateTime;

@Data
@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class MyClass{

    //The builder will generate a method for this property for us.
    private String anotherProperty;

    @Embedded
    @AttributeOverrides({
            @AttributeOverride(name = "localDateTime", column = @Column(name = "some_date_local_date_time")),
            @AttributeOverride(name = "zoneId", column = @Column(name = "some__date_zone_id"))
    })
    @Getter(AccessLevel.PRIVATE)
    @Setter(AccessLevel.PRIVATE)
    private ZonedDateTimeEmbeddable someDateInternal;

    public ZonedDateTime getSomeDate() {
        return someDateInternal.toZonedDateTime();
    }

    public void setSomeDate(ZonedDateTime someDate) {
        someDateInternal = new ZonedDateTimeEmbeddable(someDate);
    }

    public static class MyClassBuilder {
        //Prevent direct access to the internal private field by pre-creating builder method with private access.
        private MyClassBuilder shipmentDateInternal(ZonedDateTimeEmbeddable zonedDateTimeEmbeddable) {
            return this;
        }

        //Add a builder method because we don't have a field for this Type
        public MyClassBuilder someDate(ZonedDateTime someDate) {
            someDateInternal = new ZonedDateTimeEmbeddable(someDate);
            return this;
        }
    }

}

5
我找到了另一种解决方案。你可以将你的字段包装在"initiated final"包装器或代理中。最简单的方法是将其包装在AtomicReference中。
@Builder
public class Example {
    private String field1;
    private String field2;
    private final AtomicReference<String> excluded = new AtomicReference<>(null);
}

你可以通过get和set方法与其进行交互,但它不会出现在构建器中。
excluded.set("Some value");
excluded.get();

1
不确定这个帖子是否仍然有效,我刚刚在Lombok的功能请求中找到了一个解决方案,以防有人需要,因为Lombok团队很可能不会在其代码库中添加@Builder.Ignore。
@Builder
public class User{
  @ToString.Exclude
  private String passwordHash;
  // ... rest of the properties

  public static class UserBuilder {
    private UserBuilder passwordHash(final String passwordHash){
      return this;
    }
  }
}

它的效果非常好

0
我喜欢并且使用的一个方法是: 在构造函数中保留必需的参数,并通过生成器设置可选参数。适用于所需数量不是非常大的情况。
class A {
    private int required1;
    private int required2;
    private int optional1;
    private int optional2;

    public A(int required1, int required2) {
        this.required1 = required1;
        this.required2 = required2;
    }

    @Builder(toBuilder = true)
    public A setOptionals(int optional1, int optional2) {
        this.optional1 = optional1;
        this.optional2 = optional2;
        return this;
    }
}

然后用以下方式构建

A a = new A(1, 2).builder().optional1(3).optional2(4).build();

这种方法的好处是可选项也可以有默认值。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接