[Product update] Infrastructure Analysis Tool is now available with Business Accounts.Learn More

x
Immutable Class Builders
 
The other day I showed 1 way to make a Class #Builder more meaningful, #semantically, leveraging the #programming language's type system. Today, I'm going to address how to make them immutable, its benefits, and its drawbacks.
 
Why?
 
Class Builders typically use mutable architectures where the programming language allows. In #Java we often see the following model:
 
@Immutable  
final Class Person {  
 
public final Name name;  
public final Address address;  
 
Person(final Builder builder) {  
  this.name = builder.name:  
  this.address = builder.getAddress();  
}  
 
}  
   
@Mutable
final class Builder {  
 
public final Name name;
@Nullable private Address address;
 
private Builder(final Name name) {
  this.name = name:  
  this.address = Address.empty();  
}  
 
public static Builder named (final Name name) {  
  return new Builder(name);  
}  
 
public Builder with (@Nullable final Address address) {
  this.address = (null == address) ? Address.empty() : address;
}  
 
public Address getAddress () {
  return (null == this.address) ? Address.empty() : this.address;
}  
 
public Person build () {  
  return new Person(this);
}  
 
}  
 
Whatever Person field is required, we find in the Builder's constructor. In the above example, that's Name.
 
Other fields are optional, and their value is added to a Person instance by using one if the Builder.with() instance methods.
 
Note how the Builder also features a getAddress() method? That's because the Builder's address field is mutable, nullable, and not final.
 
This I've seen dozens of times in examples and other people's Java code. And I think we can do better.
 
So, what's wrong with the mutable Class Builder?
 
For starters, to guarantee that an optional field value isn't null, we are forced to perform null checks, and provide alternatives. This may apply only to programming languages that allow a null value, like Java, C#, c++, PHP, JavaScript, etc.
 
By the time the Person constructor is invoked, we want to have all values set in stone. No more calibrating or checking or validating. That's become the Builder's job. But with a mutable Builder, the Person class doesn't really have a choice: it can't trust that a programmer used the Builder as expected. Maybe they erased a mutable field's value, somewhere.
 
And also: really? A getter? What's this, JavaBeans? Such a waste of time. Can't we generate that code?  
 
Well. We'll see later, that switching to #immutable Builders introduces alternate wastes of time.
 
How?
 
In order to make our Builder immutable, we have to make all its fields final. Bonus: final fields can be public, so the getter can be removed.
 
Unfortunately, final instance fields can be set only once: in the constructor. So this:
 
@Nullable private Address address;

public Builder with (@Nullable final Address address) {
  this.address = (null == address) ? Address.empty() : address;
}  
 
public Address getAddress () {
  return (null == this.address) ? Address.empty() : this.address;
}  
 
becomes this:
 
public final Address address;

public Builder with (@Nullable final Address address) {
  return new Builder(this, (null == address) ? Address.empty() : address);  
}  
 
private Builder (final Builder original, final Address address) {  
  this.name = original.name;
  this.address = address;
}  
   
We were forced to introduce a Copy Constructor. And that's the drawback: if you have a Builder for 16 fields, you're going to be creating 16 copy constructors instead of 16 getters.
 
Is that worth it? What if you forget a field?
 
Well. Yes, it's worth it. The immutable, final fields provide a guarantee that their value isn't going to and hasn't been changed since instantiation. That reduces concurrency problems in multithreaded applications (which Java 8's #parallelStream makes so tenable).
 
And if you forget to set a field in one of the copy constructors, the IDE will complain (and so will the compiler if you don't use an IDE), that a final field may not have been initialized.
 
And, if you leverage value typing like I discussed the other day, you reduce the chance of assigning the wrong value to the wrong field.
 
So, erm, copy constructors, hey? Isn't that going to create lots and lots of tiny, ahort-lived memory objects, creating a lot of garbage that needs collecting, slowing down the application? What about stop-the-world garbage collections?
 
Fair enough. Personally I haven't seen that happen, but then again I tune my garbage collectors to favor short-lived, small memory objects. That'll be a topic for a different time. And, the point is moot for languages that don't use garbage collecting for memory.
 
But isn't allocating new objects significantly slower than mutating existing fields?
 
No. That's a myth. And unless you can prove otherwise, I won't be stunting my applications.
 
Happy Building!
0
LVL 27

Expert Comment

by:Brian B
Having read your other posts, you might be better to try and put some of your information together and make an article. That way you can get points.

By the way, Experts Exchange doesn't use hashtags and they aren't indexed, so not much point in putting them in.
1
LVL 2

Author Comment

by:A.E. Veltstra
Thank you, Brian! I'll look into that.
0
LVL 2

Author Comment

by:A.E. Veltstra
Anton Shipilev showed in 2014 that using all final fields and setting them in the class constructor did in fact slow down initialization when using Oracle's HotSpot Java compiler on ARM and PowerPC. I have yet to see whether his recommendations have been committed. https://shipilev.net/blog/2014/all-fields-are-final/
0

Keep in touch with Experts Exchange

Tech news and trends delivered to your inbox every month