Java and immutability
Java is sprinkled with classes that are immutable. Wrapper classes especially make for a good example. A co-worker recently asked me “Give me an example of immutability.”. “The String class” I answered, to which he scoffed. He had a good reason however. Consider this code…
public class GenericTest { public static void main(String... args) { new GenericTest().go(); } public void go() { String name = "David"; name.replaceAll("D", "d"); System.out.println(name); } }
Those of you with a keen eye for detail would have quickly seen the problem with this code. If you didnt catch it, dont blame yourself. The String “name” is immutable and the replaceAll() method creates a new String with all the ‘D’s replaced with ‘d’. It appears as though the String has been mutated, but in reality nothing has changed. The problem has a simple fix. Simply change a line of code.
name = name.replaceAll("D", "d");
The bigger problem is with the way the API works. The replaceAll() method suggests that the class is mutable while it really is not. It gives the illusion of mutability which can confuse a lot of developers (me included). You are better off leaving the mutable operations to StringBuilder. When a String needs to be mutated, there is always the StringBuilder(String str) constructor that can help.
The problem is not isolated to Strings. Integer autoboxing and unboxing for example, take place when a ‘++’ operation is executed on an Integer wrapper reference. Each ‘++’ operation creates a new Integer while giving the illusion of mutability.
Take some lessons from the code above and try to keep immutable classes, well… immutable. There is nothing technically wrong per se with the code. But when it comes to usability, confusing method names / operations can cause lost time. Besides innumerable additional objects are created from these operations.
Good point! It can confuse many people.
Well it is so basic JAVA that i’ll say it is even good if people have problem with this code. That make them realize that string are immutable if they didn’t yet.
You should always know if something is mutable or not, this is really fundamental. Most of our code expose objects via a public facade, or send object to API. You should know if you need to make a copy of theses objects before doing so and what are the risks.
It is even more important today with concurrent programing.
Same way you will suggest to functional people (lisp/clojure/scala) that their lists are bad example of immutability because you can have things like append/car/cdr? Having operations which sound like mutable but in reality return immutable copies of object with modification applied is very basic idea in real world immutability. How do you imagine creating a system where all objects are immutable if you don’t allow this?
The problema with Java and many languages is that you can just ignore a result value using a function like a method.
Perhaps, if you can’t call a function ignoring their result, except when the function return void, e.g. it’s a method.
Ignoring result values is dangerous, but nice at some times. I think it’s a very bad idea, and most languages do the same
I would will prefer an stricter rule about this: if a funciont returns a value (it’s a real function, not a method), then you CAN’T ignore their result, but most languages allow it.
Another “nice example”: Adding BigDecimals.
BigDecimal total = BigDecimal.ZERO;
total.add(anotherBigDecimal); <– don't work, bad idea, and you have no error from compiler, not even a warning.
The correct thing is:
total = total.add(anotherBigDecimal);
I have seen so many bugs from this…
@gorlok
I forget to mention: examples like this show how important are tools like findbugs or pmd to found potential problems like this. But sometimes, the bug is hidden in a closed library, waiting to explote at your face.
When designing an API that is immutable, you should give hints to the immutable nature.
number.negate(); // mutable number being negated
number.negated(); // immutable number being negated
ie. the past tense implies immutability and therefore the need to assign
number = number.negated();
Similarly, plus() is different to add(), where the latter is associated with mutability and the former (to some degree) with immutabiilty
Finally, see Joda-Time for what has become a canonical immutable library in Java.
date.setYear(year); // (not Joda-Time), implies mutation
date.withYear(year); // (Joda-Time) implies immutability, and therefore:
date = date.withYear(year);
@Nicolas
Some folks hit this problem despite realizing that a String is immutable. Method names can be misleading.
@Artur Biesiadowski
My experience with functional languages is not as vast as I would wish it to be in order to answer your question. StringBuilder (java) is a good example of using function chaining (.append(“a”).append(“b”)) to mutate an instance. I am not sure how other languages achieve this, but method names that suggest implied mutability are always confusing (at least to me).
@gorlok
There are cases where the return value from a method is of no use to a programmer. Enforcing the usage of return values does not seem like the right path to me.
@Stephen Colebourne
I have not used JODA time. I agree that a method should indicate / hint at mutation.
Also, quoting a commenter from an external site
I agree that the API would document the method / class correctly. It is how the programmer perceives these methods that is worrisome.
It is just so important to know what is immutable and what isn’t.
Changing a reference variable’s reference apparently looks like Strings are immutable while they arent!
Like for eg,
string a = “java”;
a = a.concat(” rocks”);
the string “java” is still immutable and is lost.
However the new reference of a is pointing to “java rocks”