A Generic Builder Pattern for Simplifying Java Object Creation
This article demonstrates how to replace verbose Java object instantiation and setter calls with a reusable generic Builder that supports chained property setting via functional interfaces, showing both a simple example and a full implementation supporting up to three parameters per setter.
Java developers often face the "do you have a girlfriend?" metaphor, where creating an object with many attributes becomes cumbersome using the traditional new operator followed by multiple setter calls.
First, a naive GirlFriend class is shown with a few fields and a main method that creates an instance and sets its properties one by one:
public class GirlFriend {
private String name;
private int age;
// ... getters & setters omitted
public static void main(String[] args) {
GirlFriend myGirlFriend = new GirlFriend();
myGirlFriend.setName("小美");
myGirlFriend.setAge(18);
}
}When the class grows with many more attributes (bust, waist, hips, hobby list, birthday, address, mobile, email, hairColor, gift map, etc.), the instantiation and property‑setting code becomes long and hard to maintain.
public class GirlFriend {
private String name;
private int age;
private int bust;
private int waist;
private int hips;
private List<String> hobby;
private String birthday;
private String address;
private String mobile;
private String email;
private String hairColor;
private Map<String, String> gift;
// ... getters & setters omitted
public static void main(String[] args) {
GirlFriend myGirlFriend = new GirlFriend();
myGirlFriend.setName("小美");
myGirlFriend.setAge(18);
myGirlFriend.setBust(33);
myGirlFriend.setWaist(23);
myGirlFriend.setHips(33);
myGirlFriend.setBirthday("2001-10-26");
myGirlFriend.setAddress("上海浦东");
myGirlFriend.setMobile("18688888888");
myGirlFriend.setEmail("[email protected]");
myGirlFriend.setHairColor("浅棕色带点微卷");
// ... more setters
}
}The drawbacks are obvious: separate instantiation and property assignment, repetitive code, and poor readability.
To solve this, the article introduces a universal generic Builder that works for any class without modifying the original class or requiring Lombok.
Usage example with the same GirlFriend class:
public class GirlFriend {
// ... properties omitted
public void addHobby(String hobby) { /* add hobby */ }
public void addGift(String day, String gift) { /* add gift */ }
public void setVitalStatistics(int bust, int waist, int hips) { /* set three stats */ }
public static void main(String[] args) {
GirlFriend myGirlFriend = Builder.of(GirlFriend::new)
.with(GirlFriend::setName, "小美")
.with(GirlFriend::setAge, 18)
.with(GirlFriend::setVitalStatistics, 33, 23, 33)
.with(GirlFriend::setBirthday, "2001-10-26")
.with(GirlFriend::setAddress, "上海浦东")
.with(GirlFriend::setMobile, "18688888888")
.with(GirlFriend::setEmail, "[email protected]")
.with(GirlFriend::setHairColor, "浅棕色带点微卷")
.with(GirlFriend::addHobby, "逛街")
.with(GirlFriend::addHobby, "购物")
.with(GirlFriend::addHobby, "买东西")
.with(GirlFriend::addGift, "情人节礼物", "LBR 1912女王时代")
.with(GirlFriend::addGift, "生日礼物", "迪奥烈焰蓝金")
.with(GirlFriend::addGift, "纪念日礼物", "阿玛尼红管唇釉")
.build();
}
}The generic Builder class is implemented as follows, supporting up to three parameters per setter via functional interfaces:
/**
* Generic Builder implementation
*/
public class Builder<T> {
private final Supplier<T> instantiator;
private List<Consumer<T>> modifiers = new ArrayList<>();
public Builder(Supplier<T> instantiator) {
this.instantiator = instantiator;
}
public static <T> Builder<T> of(Supplier<T> instantiator) {
return new Builder<>(instantiator);
}
public <P1> Builder<T> with(Consumer1<T, P1> consumer, P1 p1) {
Consumer<T> c = instance -> consumer.accept(instance, p1);
modifiers.add(c);
return this;
}
public <P1, P2> Builder<T> with(Consumer2<T, P1, P2> consumer, P1 p1, P2 p2) {
Consumer<T> c = instance -> consumer.accept(instance, p1, p2);
modifiers.add(c);
return this;
}
public <P1, P2, P3> Builder<T> with(Consumer3<T, P1, P2, P3> consumer, P1 p1, P2 p2, P3 p3) {
Consumer<T> c = instance -> consumer.accept(instance, p1, p2, p3);
modifiers.add(c);
return this;
}
public T build() {
T value = instantiator.get();
modifiers.forEach(modifier -> modifier.accept(value));
modifiers.clear();
return value;
}
@FunctionalInterface
public interface Consumer1<T, P1> { void accept(T t, P1 p1); }
@FunctionalInterface
public interface Consumer2<T, P1, P2> { void accept(T t, P1 p1, P2 p2); }
@FunctionalInterface
public interface Consumer3<T, P1, P2, P3> { void accept(T t, P1 p1, P2 p2, P3 p3); }
}This implementation allows easy extension to more parameters by adding additional functional interfaces. The article concludes by encouraging readers to apply the Builder to their own objects.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Architect Essentials
Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
