【问题标题】:How to exclude property from Lombok builder?如何从 Lombok builder 中排除财产?
【发布时间】:2015-08-23 10:06:12
【问题描述】:

我有一个名为 "XYZClientWrapper" 的类,它具有以下结构:

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

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

Lombok 是否支持这样的用例?

【问题讨论】:

    标签: java builder lombok


    【解决方案1】:

    要从构建器中排除字段,请尝试使用@Builder.Default

    【讨论】:

      【解决方案2】:

      我之前使用的一种方法是将实例字段分组为配置字段和会话字段。配置字段作为类实例并且对 Builder 可见,而 Session 字段进入 nested private static class 并通过具体的 final 实例字段访问(默认情况下,Builder 将忽略该字段) .

      类似这样的:

      @Builder
      class XYZClientWrapper{
          private String name;
          private String domain;
       
          private static class Session {
              XYZClient client;
          }
      
          private final Session session = new Session();
      
          private void initSession() {
              session.client = ...;
          }
       
          public void foo() {
              System.out.println("name: " + name);
              System.out.println("domain: " + domain;
              System.out.println("client: " + session.client);
          }
      }
      
      

      【讨论】:

        【解决方案3】:

        工厂静态方法示例

        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,除非 @调用 987654327@,然后执行构造函数静态方法 of 中的分配逻辑。

        另外,注意 of 方法是私有访问的,因此 build 方法是调用者可见的唯一方法

        【讨论】:

          【解决方案4】:

          我有另一种方法使用@DelegateInner Class,它支持排除字段的“计算值”。

          首先,我们将要排除的字段移动到 Inner Class 中,以避免 Lombok 将它们包含在 Builder 中。

          然后,我们使用@Delegate 来公开构建器排除字段的 Getter/Setter。

          示例:

          @Builder
          @Getter @Setter @ToString
          class Person {
          
              private String name;
              private int value;
              /* ... More builder-included fields here */
          
              @Getter @Setter @ToString
              private class BuilderIgnored {
          
                  private String position; // Not included in the Builder, and remain `null` until p.setPosition(...)
                  private String nickname; // Lazy initialized as `name+value`, but we can use setter to set a new value
                  /* ... More ignored fields here! ... */
          
                  public String getNickname(){ // Computed value for `nickname`
                      if(nickname == null){
                          nickname = name+value;
                      }
                      return nickname;
                  }
                  /* ... More computed fields' getters here! ... */
          
              }
              @Delegate @Getter(AccessLevel.NONE) // Delegate Lombok Getters/Setters and custom Getters
              private final BuilderIgnored ignored = new BuilderIgnored();
          
          }
          

          positionnickname 实际上是内部类的字段,这对 Person 类的外部是透明的。

          Person p = Person.builder().name("Test").value(123).build();
          System.out.println(p); // Person(name=Test, value=123, ignored=Person.BuilderIgnored(position=null, nickname=Test123))
          p.setNickname("Hello World");
          p.setPosition("Manager");
          System.out.println(p); // Person(name=Test, value=123, ignored=Person.BuilderIgnored(position=Manager, nickname=Hello World))
          

          优点:

          • 不要强制排除的字段为final
          • 支持排除字段的计算值
          • 允许计算字段引用构建器设置的任何字段(换句话说,允许内部类为非静态类)
          • 不需要重复所有字段的列表(例如,在构造函数中列出除排除字段之外的所有字段)
          • 不要覆盖 Lombok 库的 @Builder(例如创建 MyBuilder extends FooBuilder

          缺点:

          • 排除的字段实际上是Inner Class的字段;但是,使用 private 标识符和适当的 Getter/Setter,您可以模仿它们,就好像它们是真实的字段一样
          • 因此,这种方法限制您使用 Getters/Setters 访问排除的字段
          • 当调用 Getter 时,计算值被延迟初始化,而不是在 .build() 时。

          【讨论】:

            【解决方案5】:

            这是我的首选解决方案。这样,您可以在最后创建您的字段client,并根据构建器先前设置的其他字段来拥有它。

            XYZClientWrapper{
                String name;
                String domain;
                XYZClient client;
                
                @Builder
                public XYZClientWrapper(String name, String domain) {
                    this.name = name;
                    this.domain = domain;
                    this.client = calculateClient();
                }
            }
            

            【讨论】:

            • 这违背了建造者的设计原则。这些字段不再是私有的或最终的。如果你想要默认值,你必须自己做。
            • 您可以将它们设为最终的和私有的
            【解决方案6】:

            使用 Lombok @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 允许更改生成的构建器类名称。上面的示例假设使用了默认的构建器类名。

            【讨论】:

              【解决方案7】:

              我找到了另一种解决方案 您可以将您的字段包装到 initiated final 包装器或代理中。 将其包装到 AtomicReference 中的最简单方法。

              @Builder
              public class Example {
                  private String field1;
                  private String field2;
                  private final AtomicReference<String> excluded = new AtomicReference<>(null);
              }
              

              您可以通过 get 和 set 方法与它内部交互,但它不会出现在 builder 中。

              excluded.set("Some value");
              excluded.get();
              

              【讨论】:

                【解决方案8】:

                我发现我能够实现静态 Builder 类的“shell”,使用私有访问修饰符添加我想要隐藏的方法,并且在 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;
                        }
                    }
                
                }
                

                【讨论】:

                • 很多 cmets @AbhijitSarkar,但没有真正的答案。
                • 那是因为我不发布黑客作为答案,因此 cmets。
                • ^^ :) 这是程序员的网站,不是喜剧频道。使用图书馆的记录设施几乎不是黑客。尽管有评论,但我的代码继续运行并在生产环境中执行应有的操作。人们之所以贬低他人,是因为在内心深处,他们没有安全感,而击倒他人是他们感到安全的唯一途径。用负面评论浏览每个答案而不发布更好的答案,充其量只是拖钓。否则我不会被说服。
                • 有人迫切需要验证。
                【解决方案9】:

                在代码中创建构建器并为您的属性添加私有设置器。

                @Builder
                XYZClientWrapper{
                    String name;
                    String domain;
                    XYZClient client;
                
                    public static class XYZClientWrapperBuilder {
                        private XYZClientWrapperBuilder client(XYZClient client) { return this; }
                    }
                }
                

                【讨论】:

                • client 参数发生了什么,它被忽略了。
                • 迄今为止最好的解决方案。
                【解决方案10】:

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

                @Builder
                public class MyClass {
                   private String myField;
                
                   private final String excludeThisField = "bar";
                }
                

                龙目岛 1.16.10

                【讨论】:

                • 感谢斯蒂芬的回答。就我而言,它不起作用,因为我想忽略的字段是从父类派生的。
                • 什么?!我每天都使用带有最终字段的构建器,它们怎么能被忽略? :D 当前使用版本 1.16.12
                • @Stephan 这怎么可能是真的?我的意思是:构建器模式的目的是避免构造函数具有所有这些冗长的参数列表。字段是 final 无关:构建器的目的是以“链接方法方式”收集所有参数,而不是一个巨大的凹凸。此外,我检查并添加 final 不会影响构建器的构造。
                • 完美! @Builder 仅“添加”没有值的最终字段:final String myField - 存在,final String myField="" - 被忽略。 :)
                • 请记住,并非所有内容都是线程安全的,因此将其设为 public static 并不总是最好的主意。此外,像这样初始化属性只适用于琐碎的情况,而不是在需要考虑其他字段的值时。
                【解决方案11】:

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

                披露:我是 Lombok 开发人员。

                【讨论】:

                • 就我而言,感谢您透露您是 lombok 开发人员 ;-)。这个构造函数或静态(工厂)方法可以是非公开的吗?
                • 我偶然发现了这个问题和答案,因为我有同样的问题。就我而言,构造函数解决方案并不是一个很好的解决方案。我有一个具有 29 个属性(一个实体)的类,并且只希望其中一个不包含在构建器中(实际上是:id)。有没有办法像@ToString 那样排除机制?使用 Lombok 的主要原因是避免混乱。 28个参数的构造函数不是我想看到的。
                • @Staratnight 这个答案可能对你有帮助:stackoverflow.com/a/39920328/363573
                • 有没有计划在@Builder注解或@Builder.Ignore字段中添加类似ignore属性的东西?
                • 如果字段已初始化为默认值则不起作用。您不能重新分配最终字段。 IMO,在Builder 上提供ignore 属性只是常识。
                猜你喜欢
                • 1970-01-01
                • 2012-11-09
                • 1970-01-01
                • 1970-01-01
                • 2020-03-02
                • 1970-01-01
                • 2021-05-03
                • 2013-11-27
                • 2018-05-05
                相关资源
                最近更新 更多