Java immutable-class rules
Is the below class immutable:
final class MyClass {
private final int[] array;
publ开发者_开发百科ic MyClass(int[] array){
this.array = array;
}
}
No it is not because the elements of the array can still be changed.
int[] v1 = new int[10];
MyClass v2 = new MyClass(v1);
v1[0] = 42; // mutation visible to MyClass1
My two cents regarding immutability rules (which I retained from reading Effective Java - a great book!):
- Don't provide methods that can modify the state of an object.
- Make all your fields final.
- Make sure that your class is non-extendable.
- Make all your fields private.
- Provide exclusive access to any fields or components of your class that can be changed. Essentially this applies to your situation (as explained by JaredPar). A person that uses your class still has a reference to your array. The opposite is the case where you return a reference to an component of your class. In this case, always create defensive copies. In your case, you should not assign the reference. Instead, copy the array that the user of your class provides, into your internal component.
"Immutability" is a convention between the programmer and himself. That convention may be more or less enforced by the compiler.
Instances of a class are "immutable" if they do not change during the normal course of the application code execution. In some cases we know that they do not change because the code actually forbids it; in other cases, this is just part of how we use the class. For instance, a java.util.Date
instance is formally mutable (there is a setTime()
method on it) but it is customary to handle it as if it were immutable; this is just an application-wide convention that the Date.setTime()
method shall not be called.
As additional notes:
- Immutability is often thought of in terms of "external characteristics". For instance, Java's
String
is documented to be immutable (that's what the Javadoc says). But if you look at the source code, you will see that aString
instance contains a private field calledhash
which may change over time: this is a cache for the value returned byhashCode()
. We still say thatString
is immutable because thehash
field is an internal optimization which has no effect visible from the outside. - With reflection, the most private of instance fields can be modified (including those marked as
final
), if the programmer wishes so hard enough. Not that it is a good idea: it may break assumptions used by other pieces of code using the said instance. As I said, immutability is a convention: if the programmer wants to fight himself, then he can, but this can have adverse side-effects on productivity... - Most Java values are actually references. It is up to you to define whether a referenced object is part of what you consider to be "the instance contents". In your class, you have a field which references an (externally provided) array of integers. If the contents of that array are modified afterwards, would you consider that this breaks immutability of your
MyClass
instance ? There is no generic answer to that question.
There is no way to make an array immutable. That is there is no way to keep any client code from setting or removing or adding items to the array.
Here is a truly immutable alternative:
private static class MyClass
{
private List<Integer> list;
private MyClass(final int[] array)
{
final List<Integer> tmplist = new ArrayList<Integer>(array.length);
for (int i : array)
{
tmplist.add(array[i]);
}
this.list = Collections.unmodifiableList(tmplist);
}
}
To make a class immutable, you need to both ensure that all the fields on it are final, and that the types of those fields are immutable too.
This can be a pain to remember, but there is a tool to help you.
Pure4J provides an annotation @ImmutableValue
, which you can add to an interface or class.
There is a maven plugin to check at compile-time that you are meeting the rules on immutability following this.
Hope this helps.
精彩评论