Как исключить недвижимость из застройщика Ломбок?

У меня есть класс под названием «XYZClientWrapper», который имеет следующую структуру:

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

Я не хочу, чтобы функция сборки создавалась для свойства XYZClient client

Поддерживает ли Lombok такой вариант использования?


person Vivek Goel    schedule 08.06.2015    source источник


Ответы (10)


Да, вы можете разместить @Builder в конструкторе или статическом (фабричном) методе, содержащем только нужные поля.

Раскрытие информации: я разработчик Lombok.

person Roel Spilker    schedule 09.06.2015
comment
Насколько я понимаю, спасибо, что сообщили, что вы разработчик ломбока ;-). Может ли этот конструктор или статический (фабричный) метод быть закрытым? - person markvgti; 27.11.2015
comment
Да, он мог иметь любую видимость, в том числе private. Раскрытие должно указывать на то, что ответ или мнение могут быть предвзятыми. В данном случае это не так актуально, но я взял за привычку добавлять это ко всем моим ответам, связанным с ломбоком. - person Roel Spilker; 27.11.2015
comment
Я наткнулся на этот вопрос и ответил, потому что имею в виду тот же вопрос. Решение конструктора в моем случае не очень хорошее. У меня есть класс с 29 свойствами (сущность), и я хочу, чтобы только одно из них не было включено в построитель (на самом деле: идентификатор). Нет ли способа исключить механизм, как в @ToString? Основная причина использовать Ломбок - избежать беспорядка. Конструктор с 28 параметрами - это не то, что я хочу видеть. - person Staratnight; 12.04.2016
comment
Решил ли проект lombok проблему, представленную здесь @Staratnight? - person Stephan; 07.10.2016
comment
@Staratnight Этот ответ может вам помочь: stackoverflow.com/a/39920328/363573 - person Stephan; 07.10.2016
comment
Планируется ли добавить что-то вроде атрибута ignore в @Builder аннотацию или аннотацию @Builder.Ignore к полям? - person pirho; 12.10.2018
comment
@Roel Spilker: Я попытался установить его в конструкторе required-args, сгенерированном lombok (1.18.6), но это вызывает NPE в компиляторе (java8) :) @RequiredArgsConstructor(onConstructor = @__({ @Builder })) - person rudi; 17.05.2019
comment
@Staratnight Кстати, я думаю, что конструктор с 29 свойствами - повод задуматься о рефакторинге) - person stand alone; 04.12.2019
comment
Я в целом с тобой согласен. Поскольку мы используем Ломбок, чтобы помочь нам с сущностями, 29 легко достигается на центральных объектах. Даже если вы попытаетесь получить все в производных таблицах, вам нужно будет управлять внешними ключами. - person Staratnight; 05.12.2019
comment
Не работает, если поля были инициализированы значениями по умолчанию. Вы не можете переназначить финальные поля. ИМО, предоставление атрибута ignore на Builder - это просто здравый смысл. - person Abhijit Sarkar; 17.01.2020
comment
Второй ответ, использующий final, идеально подходит для меня: stackoverflow.com/a/39920328/6555159 - person socona; 11.02.2021
comment
Поскольку вы дали понять, что являетесь разработчиком Lombock, меня смущает тот факт, что вы поощряете создание конструктора, а именно этого конструктор пытается избежать. Я не знаю, читали ли вы вопрос @pirho, но это кажется следующим естественным шагом в библиотеке, и это было бы действительно полезно. - person Carlos López Marí; 10.03.2021

Кроме того, я обнаружил, что отметка поля как final, static или static final указывает @Builder игнорировать это поле.

@Builder
public class MyClass {
   private String myField;

   private final String excludeThisField = "bar";
}

Ломбок 1.16.10

person Stephan    schedule 07.10.2016
comment
Спасибо Стефану за ответ. В моем случае это не работает, поскольку поле, которое я хочу игнорировать, является производным от родительского класса. - person Staratnight; 09.10.2016
comment
Что?! Я использую конструктор ежедневно с конечными полями, как их можно игнорировать? : D сейчас использую версию 1.16.12 - person Dominik; 17.01.2017
comment
@ Стефан Как это могло быть правдой? Я имею в виду: цель шаблона построителя - избежать конструкторов со всеми этими длинными списками параметров. Окончательное значение поля не имеет ничего общего: цель конструктора - собрать все параметры в виде метода цепочки вместо одного огромного выступа. Кроме того, я это проверил, и добавление final не влияет на конструкцию застройщика. - person Piohen; 14.02.2017
comment
Что, если я не хочу, чтобы они были окончательными, и я не хочу, чтобы они были на строителе? - person Marinos An; 14.02.2019
comment
Идеально! @Builder добавляет только конечные поля без значений: final String myField - присутствует, final String myField="" - игнорируется. :) - person Cherry; 27.02.2019
comment
Помните, что не все потокобезопасно, поэтому делать это public static не всегда лучшая идея. Кроме того, подобная инициализация свойства работает только в тривиальных случаях, а не тогда, когда необходимо учитывать значения других полей. - person Abhijit Sarkar; 17.01.2020
comment
Не работает при использовании @SuperBuilder - person Flyout91; 24.02.2021
comment
@AbhijitSarkar, даже в однопоточном контексте общедоступные статические поля очень проблематичны (если они не являются неизменяемыми). Я не понимаю, как общедоступные статические поля связаны с безопасностью потоков. - person yaccob; 07.05.2021
comment
@Stephan Это потому, что они не были инициализированы. Вот почему в примере он устанавливает бар. Тот же результат с @Generated. Генератор придает ему значение и делает его неизменным. - person Nate T; 31.05.2021

Создайте конструктор в коде и добавьте частный сеттер для своей собственности.

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

    public static class XYZClientWrapperBuilder {
        private XYZClientWrapperBuilder client(XYZClient client) { return this; }
    }
}
person Bill H    schedule 06.06.2017
comment
То, что происходит с аргументом client, игнорируется. - person Abhijit Sarkar; 17.01.2020
comment
Лучшее решение на данный момент. - person Carlos López Marí; 10.03.2021

Вот мое предпочтительное решение. При этом вы можете создать свое поле client в конце и иметь его в зависимости от других полей, которые ранее были установлены конструктором.

XYZClientWrapper{
    String name;
    String domain;
    XYZClient client;
    
    @Builder
    public XYZClientWrapper(String name, String domain) {
        this.name = name;
        this.domain = domain;
        this.client = calculateClient();
    }
}
person SexyNerd    schedule 06.12.2018
comment
Это противоречит принципу проектирования строителя. Поля больше не являются закрытыми или окончательными. Если вам нужны значения по умолчанию, вам придется сделать это самостоятельно. - person Abhijit Sarkar; 17.01.2020

Для примера статического метода фабрики

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 является единственным методом, видимым вызывающим абонентам.

person Youans    schedule 16.01.2019

Я обнаружил, что смог реализовать «оболочку» статического класса 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;
        }
    }

}
person Richard Collette    schedule 31.10.2017
comment
Много комментариев @AbhijitSarkar, но нет реальных ответов. - person Richard Collette; 17.01.2020
comment
Это потому, что я не публикую хаки в качестве ответов, а значит, и комментариев. - person Abhijit Sarkar; 17.01.2020
comment
^^ :) Это сайт для программистов, а не комедийный канал. Использование задокументированных возможностей библиотеки вряд ли является хитростью. Мой код продолжает работать и делать то, что должен в производственной среде, несмотря на комментарий. Люди унижают других, потому что внутри они незащищены, и сбивать с ног других - единственный способ почувствовать себя в безопасности. Проходить каждый ответ с отрицательным комментарием и не публиковать лучший ответ - это в лучшем случае троллинг. В противном случае меня не убедят. - person Richard Collette; 17.01.2020
comment
Кто-то отчаянно нуждается в проверке. - person Abhijit Sarkar; 19.01.2020

Может помочь добавление в класс так называемого «частичного построителя» с 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 позволяет изменять сгенерированное имя класса построителя. В приведенном выше примере предполагается, что используется имя класса построителя по умолчанию.

person Dogan Ersoz    schedule 12.04.2020

Я нашел еще одно решение. Вы можете обернуть свое поле в начальную финальную оболочку или прокси. Самый простой способ обернуть его в 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();
person kattoha    schedule 21.08.2019

У меня есть другой подход, использующий @Delegate и Inner Class, который поддерживает вычисленные значения для исключенных полей.

Во-первых, мы перемещаем поля, которые нужно исключить, в Inner Class, чтобы Lombok не включил их в Builder.

Затем мы используем @Delegate, чтобы открыть геттеры / сеттеры полей, исключенных построителем.

Пример:

@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();

}

За пределами этого Person класса будет очевидно, что position и nickname на самом деле являются полями внутреннего класса.

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
  • Поддержка вычисляемых значений для исключенных полей
  • Разрешить вычисляемым полям ссылаться на любые поля, установленные построителем (другими словами, разрешить внутреннему классу быть нестатическим классом)
  • Не нужно повторять список всех полей (например, перечисление всех полей, кроме исключенных, в конструкторе)
  • Не отменять @Builder библиотеки Lombok (например, создание MyBuilder extends FooBuilder)

Минусы:

  • Исключенные поля на самом деле являются полями Inner Class; однако, используя идентификатор private с соответствующими геттерами / сеттерами, вы можете имитировать, как если бы они были настоящими полями
  • Следовательно, этот подход ограничивает доступ к исключенным полям с помощью Getters / Setters.
  • Вычисленные значения инициализируются отложенным образом при вызове геттеров, а не при .build().
person Northnroro    schedule 18.01.2021

Один из подходов, который я использовал раньше, заключался в группировке полей экземпляра в поля конфигурации и поля сеанса. Поля конфигурации идут как экземпляры классов и видны Строителю, в то время как поля сеанса входят в вложенный private static class и доступны через конкретное поле экземпляра final (которое Строитель по умолчанию игнорирует).

Что-то вроде этого:

@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);
    }
}

person MEE    schedule 03.11.2020