Recently someone at work someone sent me a link to an article saying that getters and setters are evil by a guy named Yego256. He has a few other interesting posts about immutable objects here and here. They can be added to the list I made of pages about immutable objects here.
Hopefully Grails 3 will utilize immutability.
It takes a shift in mindset to start using immutable objects. Plus, in Java (and Groovy) a lot of web frameworks don’t utilize them, so people are not too familiar with them.
One issue some developers have is: Many things in the world are mutable. How can you make “changes” to immutable objects?
One answer is that you can use the values you don’t want to change to populate a new immutable object, and your variable/field name points to a new immutable object.
(If you can think of a better way to explain this, let me know. It’s kind of confusing to say that an immutable object is referenced by a “variable name”.)
I will demonstrate this with Groovy (using the annotations EqualsAndHashCode, Immutable and ToString). I made a class to model a state in the USA. States do not change their names, abbreviations or year they joined the union. But many of them have changed their capital cities. So we will make an object, and change its capital city while keeping everything else the same.
Here is our class:
package info.shelfunit.states import groovy.transform.EqualsAndHashCode import groovy.transform.Immutable import groovy.transform.ToString @Immutable @ToString( includeNames = true ) @EqualsAndHashCode class USState { String name String abbrev String capital int yearJoined }
Now let’s look at using it in a Groovy shell. The first capital of Illinois was a town called Kaskaskia, which no longer exists. Then it moved to Vandalia (which does still exist) and finally to Springfield.
I will try to change a few fields to prove the objects are immutable, and I will call hashCode() to show that we are dealing with different objects.
groovy:000> import info.shelfunit.states.* ===> [import info.shelfunit.states.*] groovy:000> il = new USState( name: 'Illinois', abbrev: 'IL', capital: 'Kaskaskia', yearJoined: 1818) ===> info.shelfunit.states.USState(name:Illinois, abbrev:IL, capital:Kaskaskia, yearJoined:1818) groovy:000> il.name ===> Illinois groovy:000> il.name = "Indiana" ERROR groovy.lang.ReadOnlyPropertyException: Cannot set readonly property: name for class: info.shelfunit.states.USState at info.shelfunit.states.USState.setProperty (USState.groovy) at groovysh_evaluate.run (groovysh_evaluate:3) ... groovy:000> il.toString() ===> info.shelfunit.states.USState(name:Illinois, abbrev:IL, capital:Kaskaskia, yearJoined:1818) groovy:000> il.hashCode() ===> 1673606286 groovy:000> il = new USState( name: il.name, abbrev: il.abbrev, capital: 'Vandalia', yearJoined: il.yearJoined ) ===> info.shelfunit.states.USState(name:Illinois, abbrev:IL, capital:Vandalia, yearJoined:1818) groovy:000> il.abbrev ===> IL groovy:000> il.abbrev = "WI" ERROR groovy.lang.ReadOnlyPropertyException: Cannot set readonly property: abbrev for class: info.shelfunit.states.USState at info.shelfunit.states.USState.setProperty (USState.groovy) at groovysh_evaluate.run (groovysh_evaluate:3) ... groovy:000> il.toString() ===> info.shelfunit.states.USState(name:Illinois, abbrev:IL, capital:Vandalia, yearJoined:1818) groovy:000> il.hashCode() ===> -934597459 groovy:000> il = new USState( name: il.name, abbrev: il.abbrev, capital: 'Springfield', yearJoined: il.yearJoined ) ===> info.shelfunit.states.USState(name:Illinois, abbrev:IL, capital:Springfield, yearJoined:1818) groovy:000> il.capital ===> Springfield groovy:000> il.capital = "Homerville" ERROR groovy.lang.ReadOnlyPropertyException: Cannot set readonly property: capital for class: info.shelfunit.states.USState at info.shelfunit.states.USState.setProperty (USState.groovy) at groovysh_evaluate.run (groovysh_evaluate:3) ... groovy:000> il.toString() ===> info.shelfunit.states.USState(name:Illinois, abbrev:IL, capital:Springfield, yearJoined:1818) groovy:000> il.hashCode() ===> 213225124 groovy:000>
So we used the existing values to make new objects. The JVM used the same variable name (“il”) to refer to different immutable objects.
Here is the code without the output:
import info.shelfunit.states.* il = new USState( name: 'Illinois', abbrev: 'IL', capital: 'Kaskaskia', yearJoined: 1818) il.name // returns "Illinois" il.name = "Indiana" // will cause groovy.lang.ReadOnlyPropertyException il.toString() il.hashCode() // Change capital to Vandalia il = new USState( name: il.name, abbrev: il.abbrev, capital: 'Vandalia', yearJoined: il.yearJoined ) il.abbrev // returns "IL" il.abbrev = "WI" // causes groovy.lang.ReadOnlyPropertyException: il.toString() il.hashCode() // Change capital to Springfield il = new USState( name: il.name, abbrev: il.abbrev, capital: 'Springfield', yearJoined: il.yearJoined ) il.capital // returns "Springfield" il.capital = "Homerville" // causes groovy.lang.ReadOnlyPropertyException: il.toString() il.hashCode()
2015-06-06_14.23.56 update: I now realize that in order to prove the objects were truly different objects, I should have called System.identityHashCode(il) instead.
You’re welcome.
Image from the Rheinau Psalter, a 13th century manuscript housed at Central Library of Zurich. Image from e-Codices. This image is assumed to be allowed under Fair Use.