When did immutable objects become idiomatic Java? I suppose it depends on your frame of reference and what your experience brings to coding. It may not be idiomatic Java to you. In fact, Java Beans and fluent interfaces seem to rely on in-place side effects and mutability to function properly. Exactly what place do immutable objects have in the Java world?
The immutability of String surprised me when I was learning Java. One of the first programs I wrote assumed String#replace did an in-place edit of the String, one of the first debugger sessions I conducted clearly showed me that String#replace was not editing the String, and one of the first Sun Forum questions I posted was about String#replace being broken in Java. BROKEN, I say! This represents the one time when an answer to a Sun Forum question clearly solved the poster's problem without a lengthy chain of "please post your code" responses. The point is that immutability has been with Java forever. And now that the days of Object Pools and persistent performance concerns are behind us, it's time to embrace immutability in our own code.
The benefits of immutability are many and well-documented. Effective Java (either edition) is a good start. I'll touch the highlights with the example of a Person object:
class Person {
def firstName
def lastName
}
HA! Tricked you, it's Groovy. In the Groovy compiler, the Person class gains two methods for each property defined:public void setFirstName(Object)
public Object getFirstName()
...
The problem with the Class (as the JVM sees it), is that many threads may access Person and see the same instance in different states. It is difficult to share this object correctly across threads. The problem with the Java source is the cruft of writing getters and setters. The problem with the API of Person is that working effectively with the object requires many calls to setX and setY mutators just to get simple interactions working. The problem with maintaining this code is that the object is somewhat non-deterministic and difficult to reason about. Groovy uniquely solves only one of these issues: the source level cruft. Sure, you don't have to write out getters and setters, but that is the least important problem to solve.Fluent APIs solve the issue of having to munge up the source code with repetitive calls to setX and setY. Instead of following the Bean pattern of setters being void methods, instead have all your setters return a 'this' reference:
class Person {
def firstName
def lastName
def setFirstName(firstName) {
this.firstName = firstName
this
}
def setLastName(lastName) {
this.lastName = lastName
this
}
}
Now you have a nicer API to work with at the expense of not having a standard Java bean. Remember the Java language change proposal to make void methods always implicitly return a 'this' reference? I feel like I'm the only guy wanting that in Project Coin. Oh well. Here is what consuming the API now looks like:def person = new Person()
.setFirstName('hamlet')
.setLastName('darcy')
assert 'hamlet darcy' == "$person.firstName $person.lastName"
Groovy has a solution to this same problem: the with block. All object have a with method, and when inside the closure parameter, all method and field references are resolved back to the object having with invoked. Visual Basic had this feature and I always missed it in Java:def person = new Person()
person.with {
firstName = 'hamlet'
lastName = 'darcy'
}
assert 'hamlet darcy' == "$person.firstName $person.lastName"
So both Groovy and Java have an answer for the boilerplate involved with writing and consuming APIs. But again, these aren't the big issue with mutable objects: non-determinism and thread safety are. To help solve that you can use immutable objects.Can we get nice, fluent interfaces in Java without sacrificing mutability? The go-to solution in Java is the Builder idiom. Make your Person object immutable (all final fields), and the constructor private. Then define a mutable Builder class that wraps the constructor in a better API. In the end, creating Person instances looks like this:
def person = new PersonBuilder()
.setFirstName('hamlet')
.setLastName('darcy')
.build()
assert 'hamlet darcy' == "$person.firstName $person.lastName"
You instantiate the Builder and the builder instantiates the Person. This is nice... I guess maybe. Remember the idea of a "meaningless abstraction"? PersonBuilder is definitely a meaningless abstraction. It does not exist to solve any of the problems the system was designed to do. It exists solely to solve a problem in the implementation. Suspect. Very suspect.The Builder has by far the longest implementation, and the DRY principle is glaringly violated in the duplicate fields:
class Person {
private final def firstName
private final def lastName
Person(firstName, lastName) {
this.firstName = firstName
this.lastName = lastName
}
}
class PersonBuilder {
def firstName
def lastName
def build() {
new Person(firstName, lastName)
}
def setFirstName(firstName) {
this.firstName = firstName
this
}
def setLastName(lastName) {
this.lastName = lastName
this
}
}
It should be said, though, that this idiom does address the determinism and concurrency issues with mutability. Person can now be shared freely across threads, cached safely, and behaves in a more deterministic manner.The best approach has not yet been mentioned. Simply create an immutable object without a builder, and also provide a nice fluent API around it. The calling code can look exactly the same as the previous fluent API example:
def person = new Person()
.setFirstName('hamlet')
.setLastName('darcy')
assert 'hamlet darcy' == "$person.firstName $person.lastName"
You can do this by slightly tweaking the fluent API example. Don't return a 'this' reference from setters, just return an entirely new object. And don't worry about all those little objects you're creating and tossing away.class Person {
private final def firstName
private final def lastName
Person() { }
Person(firstName, lastName) {
this.firstName = firstName
this.lastName = lastName
}
def setFirstName(firstName) {
new Person(firstName, lastName)
}
def setLastName(lastName) {
new Person(firstName, lastName)
}
}
Is a Person instance threadsafe? Yes. It is immutable.Is a Person instance deterministic? Yes. No race conditions here.
Is a Person instance API easy to consume? Yup, it has a fluent API.
Does the Person class introduce a meaningless abstraction? Nope, and good thing!
Does the Person class require an exotic programming language to implement? No, this works fine in Java.
Does the Person implementation require tons of boilerplate? Maybe. The good part is that every implementation of a setter is exactly the same. The bad part is that every implementation of a setter is exactly the same. Not very DRY, but it could be automated in Groovy at runtime with a little metaprogramming.
That's it, immutable exploration over. And what place should immutability have in the Java world? It should be front and center. I hope I've shown that immutability and good APIs do not need to conflict with each other. You can get both with a tiny bit of extra work.
13 comments:
Setters returning a new instance - clever idea, indeed! I like it.
Setters returning new instances - can u make this work with Hibernate ?
You make a convincing argument to implement the builder right into the business objects. Though it it's not DRY, using exactly the same constructor call makes it less error prone.
I suggest using the withXXX syntax for immutable setters that was proposed by Stephen Colebourne (http://www.jroller.com/scolebourne/entry/immutable_pojos_improving_on_getters).
I have called the last pattern Cumulative Factory in the Practical API Design Book. I like its balance between ease of use and correctness (due to immutability).
Wouldn't named arguments solve it nicer? You would just put parameters in constructor
new Person(
firstName:"Hamlet",
lastName:"Darcy"
)
Readable, no need for builder class, no need for custom logic in builder to check if you have specified all required parameters,etc.
Imagine your builder example, if you add 3rd required parameter (date of birth lets say). With builder code, you will not get compilation errors.
Yes, to get named parameters in constructors, you will need to use scala 2.8+ - look at the case classes to see it done right way.
@Debasish Sorry, have no idea if Hibernate supports this. Probably not. :(
@fmaul I like Stephen's idea there. I remember reading this I'd just forgotten!
@Artur Named parameters only works at object construction time. In my simple examples named parameters are a replacement. But sometimes I do want to update an object long after it is created. For that I need a new instance. Groovy has named parameters, but I left it out of the example b/c it wasn't what I want.
@Jaroslav - Your book's been on my to-read list for a long time... I'll have to pick it up!
@Hamlet;
In scala you would use
Person p1 = Person( firstName:"Hamlet",
lastName:"Darcy");
Person p2 = p1 copy (firstName:"Rosenkratz");
(check http://programming-scala.labs.oreilly.com/ch06.html and look for 'copy')
and definition of Person is just
case class Person(val firstName: String, val lastName:String) {}
I don't want to turn it into groovy versus scala war - just answering to your remark. Copy method above is combination of named arguments together with default arguments - it just defines all arguments to be same as for 'this' by default and allows you to override some of them by name.
I personally find it bit more elegant than having setter methods returning copies (too easy to get confused in same way as you described with 'replace' method - especially given the fact that setters ARE mutating in normal java). If at all, I would probably name the methods withFirstName/withLastName instead of reusing setter convention.
Thanks artur, I did not know that!
Sorry Hamlet,
I think have lost some logical step. The Fluent Interface is an application of Builder pattern, what use a lot the set mechanism (too in different form that in standard syntax). I nthe same time you say the the immutability is a great thing: but the immutability want there'snt any set mechanism.
Also in yout code you write:
def setFirstName(firstName) {
this.firstName = firstName
this
}
but is a better practise to return a copy of this?
bye
Very nice article.
You should also check out what Groovy now has to offer in this area: groovy.codehaus.org/Immutable+AST+Macro
So now the declaration becomes as easy as:
@Immutable final class Person {
String firstName, lastName
}
That's it. Groovy takes care of the rest of the immutable guarantees for you. Beautiful.
@alepuzio I was demonstrating a traditional fluent api/builder.
When your classes have invariants to satisfy this approach in most situations will not work because the setters will return objects which will violate them or would throw an exception when they are verified in the constructor.
@Sven I'm sure there is a time where this approach will not work... but it is important to remember that just because an object changes over time does not mean that it requires mutability. Some people don't know this! Knowing that there is an alternative to a bunch of mutable setX methods is the first step towards being able to balance the trade-offs between mutable and immutable objects.
Post a Comment