If my class is going to be an immutable one it has to be final and without any method that modifies its state and all properties must be private. But why should it have properties declared as final (for example, private final int a)?
Edit
Will the class still be an immutable one if it has a reference to objects that are not immutable?
解决方案
From Jeremy Manson's blog post Immutability in Java:
Now, in common parlance, immutability
means "does not change". Immutability
doesn't mean "does not change" in
Java. It means "is transitively
reachable from a final field, has not
changed since the final field was set,
and a reference to the object
containing the final field did not
escape the constructor".
Manson co-authored the Java Memory Model spec, so should be knowing what he is talking about.
An example from the Brian Goetz's Java Concurrency in Practice book:
public Holder holder;
public void initialize() { holder = new Holder(42); }
publc class Holder {
private int n;
public Holder(int n) { this.n = n; }
public void assertSanity() {
if (n != n) {
throw new AssertionError(”This statement is false”);
}
}
}
// Thread 1: // Thread 2:
initialize(); if (holder != null) holder.assertSanity();
Thread 2 above can throw the AssertionError. To avoid such visibility issues the field n of Holder class should have been declared final.
EDIT: Consider the sequence of steps:
Allocate memory for new object of type Holder
Initialize field n of the new object
Execute the constructor
Assign the reference of the new object to holder reference
This is the "sane" sequence of events and the JMM (Java Memory Model) guarantees that this is what the thread calling initialize() will see. But, since there is no synchronization, JMM makes no guarantees about what sequence the second thread calling holder.assertSanity() will see. In particular, the AssertionError will be thrown if the second thread sees the sequence:
holder is non-null, so that it could call assertSanity method
The first read of n results in 0, the default value of an int field
The second read of n results 42
In short, when an object's constructor returns in one thread, another thread might not see all field initializations of that object. To guarantee that the second thread sees the correctly initialized fields, you should either make them final, or do a mutex unlock (i.e. initialize() method should be synchronized) or do a volatile write (i.e. holder should be volatile).