The crucial thing to understand is the concept of an immutable type.
Let's say I have this code:
public class UnitedStates {
private static final List<String> STATE_NAMES =
Arrays.asList("Washington", "Ohio", "Oregon", "... etc");
public static List<String> getStateNames() {
return STATE_NAMES:
}
}
Looks good, right?
Nope! This code is broken! See, I could do this, whilst twirling my moustache and wielding a monocle:
UnitedStates.getStateNames().set(0, "Turtlia"); // Haha, suck it washington!!
and that will work. Now for ALL callers, apparently there's some state called Turtlia. Washington? Wha? Nowhere to be found.
The problem is that Arrays.asList returns a mutable object: There are methods you can invoke on this object that change it.
Such objects cannot be shared with code you don't trust, and given that you don't remember every line you ever wrote, you can't trust yourself in a month or two, so, you basically can't trust anybody. If you want to write this code properly, all you had to do is use List.of instead of Arrays.asList, because List.of produces an immutable object. It has zero methods that change it. It seems like it has methods (it has a set method!), but try invoking it. It won't work, you'll get an exception, and crucially, the list does not change. It is in fact impossible to do so. Fortunately, String is also immutable.
Immutables are much easier to reason about, and can be shared freely with whatever you like without copying.
So, want your own immutable? Great - but apparently the only way to make one, is to have a constructor where all values are set and that's it - immutable types cannot have set methods, because that would mutate them.
If you have a lot of fields, especially if those fields have the same or similar types, this gets annoying fast. Quick!
new Bridge("Golden Gate", 1280, 1937, 2737);
when was it built? How long is it? What's the length of the largest span?
Uhhhhhhh..... how about this instead:
newBridge()
.name("Golden Gate")
.longestSpan(1280)
.built(1937)
.length(2737)
.build();
sweet. Names! builders also let you build over time (by passing the builder around to different bits of code, each responsible for setting up their bits). But a bridgebuilder isn't a bridge, and each invoke of build() will make a new one, so you keep the general rules about immutability (a BridgeBuilder is not immutable, but any Bridge objects made by the build() method are.
If we try to do this with setters, it doesn't work. Bridges can't have setters. you can have 'withers', where you have set-like methods that create entirely new objects, but, calling these 'set' is misleading, and you create both a ton of garbage (rarely relevant, the GC is very good at collecting short lived objects), and intermediate senseless bridges:
Bridge goldenGate = Bridge.create().withName("Golden Gate").withLength(2737);
somewhere in the middle of that operation you have a bridge named 'Golden Gate', with no length at all.
In fact, the builder can decide to not let you build() bridge with no length, by checking for that and throwing if you try. This process of invoking one method at a time can't do that. At best it can mark a bridge instance as 'invalid', and any attempt to interact with it, short of calling .withX() methods on it, results in an exception, but that's more effort, and leads to a less discoverable API (the with methods are mixed up with the rest, and all the other methods appear to throw some state exception that is normally never relevant.. that feels icky).
THAT is why you need builders.
NB: Project Lombok's @Builder annotation gives you builders for no effort at all. All you'd have to write is:
import lombok.Value;
import lombok.Builder;
@Value @Builder
public class Bridge {
String name;
int built;
int length;
int span;
}
and lombok automatically takes care of the rest. You can just Bridge.builder().name("Golden Gate").span(1280).built(1937).length(2737).build();.