如何将房产排除在龙目岛建筑商之外?

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

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

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

龙目岛是否支持这种用例?

83213 次浏览

Yes, you can place @Builder on a constructor or static (factory) method, containing just the fields you want.

Disclosure: I am a Lombok developer.

Alternatively, I found out that marking a field as final, static or static final instructs @Builder to ignore this field.

@Builder
public class MyClass {
private String myField;


private final String excludeThisField = "bar";
}

Lombok 1.16.10

Create the builder in code and add a private setter for your property.

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


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

I found that I was able to implement a "shell" of the static Builder class, add the method I want to hide with a private access modifier, and it is no longer accessible in the builder. Likewise I can add custom methods to the builder as well.

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


}

Here is my preferred solution. With that, you can create your field client at the end and have it depending on other fields that previously set by the builder.

XYZClientWrapper{
String name;
String domain;
XYZClient client;
    

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

For factory static method example

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
}
}

then you can use as follows:

Car toyotaCorollaCar=Car.builder().name("Toyota").model("Corolla").build();
// You can see now that Car.builder().engine() is not available

Notice the static method of will be called whenever build() is called, so doing something like Car.builder().name("Toyota") won't actually set the value "Toyota" into name unless build() is called and then assigning logic within the constructor static method of is executed.

Also, Notice that the of method is privately accessed so that build method is the only method visible to the callers

I found one more solution You can wrap your field into initiated final wrapper or proxy. The easiest way to wrap it into AtomicReference.

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

You can interact with it inside by get and set methods but it won't be appeared in builder.

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

Adding a so called 'partial builder' to the class with Lombok @Builder can help. The trick is to add a inner partial builder class like this:

@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 allows the generated builder class name to be changed. The example above assumed the default builder class name is used.

One approach I have used before was to group instance fields into Configuration fields and Session fields. Configuration fields go as class instances and are visible to the Builder, while Session fields go into a nested private static class and are accessed via a concrete final instance field (which the Builder will ignore by default).

Something like this:

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


I have another approach using @Delegate and Inner Class, which supports "computed values" for the excluded fields.

First, we move the fields to be excluded into an Inner Class to avoid Lombok from including them in the Builder.

Then, we use @Delegate to expose Getters/Setters of the builder-excluded fields.

Example:

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


}

It will be transparent to outside of this Person class that position and nickname are actually inner class' fields.

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))

Pros:

  • Do not force the excluded fields to be final
  • Support computed values for the excluded fields
  • Allow computed fields to refer to any fields set by the builder (In other words, allow the inner class to be non-static class)
  • Do not need to repeat the list of all fields (Eg. listing all fields except the excluded ones in a constructor)
  • Do not override Lombok library's @Builder (Eg. creating MyBuilder extends FooBuilder)

Cons:

  • The excluded fields are actually fields of Inner Class; however, using private identifier with proper Getters/Setters you can mimic as if they were real fields
  • Therefore, this approach limits you to access the excluded fields using Getters/Setters
  • The computed values are lazy initialized when Getters are invoked, not when .build().

To exclude field from builder, try using @Builder.Default

One method I like and use is this. Keep required parameters in constructor, and set optional through builder. Works if number of required is not very big.

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

And then construct it with

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

Nice thing with this approach is that optionals can also have default value.