Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

immutable objects and lazy initialization.

Tags:

java

http://www.javapractices.com/topic/TopicAction.do?Id=29

Above is the article which i am looking at. Immutable objects greatly simplify your program, since they:

allow hashCode to use lazy initialization, and to cache its return value

  • Can anyone explain me what the author is trying to say on the above line.
  • Is my class immutable if its marked final and its instance variable still not final and vice-versa my instance variables being final and class being normal.
like image 387
Kevin Avatar asked Dec 06 '22 05:12

Kevin


2 Answers

As explained by others, because the state of the object won't change the hashcode can be calculated only once.

The easy solution is to precalculate it in the constructor and place the result in a final variable (which guarantees thread safety).

If you want to have a lazy calculation (hashcode only calculated if needed) it is a little more tricky if you want to keep the thread safety characteristics of your immutable objects.

The simplest way is to declare a private volatile int hash; and run the calculation if it is 0. You will get laziness except for objects whose hashcode really is 0 (1 in 4 billion if your hash method is well distributed).

Alternatively you could couple it with a volatile boolean but need to be careful about the order in which you update the two variables.

Finally for extra performance, you can use the methodology used by the String class which uses an extra local variable for the calculation, allowing to get rid of the volatile keyword while guaranteeing correctness. This last method is error prone if you don't fully understand why it is done the way it is done...

like image 70
assylias Avatar answered Dec 18 '22 07:12

assylias


If your object is immutable it can't change it's state and therefore it's hashcode can't change. That allows you to calculate the value once you need it and to cache the value since it will always stay the same. It's in fact a very bad idea to implement your own hasCode function based on mutable state since e.g. HashMap assumes that the hash can't change and it will break if it does change.

The benefit of lazy initialization is that hashcode calculation is delayed until it is required. Many object don't need it at all so you save some calculations. Especially expensive hash calculations like on long Strings benefit from that.

class FinalObject {
    private final int a, b;
    public FinalObject(int value1, int value2) {
        a = value1;
        b = value2;
    }

    // not calculated at the beginning - lazy once required
    private int hashCode;
    @Override
    public int hashCode() {
        int h = hashCode; // read
        if (h == 0) {
            h = a + b;    // calculation
            hashCode = h; // write
        }
        return h;         // return local variable instead of second read
    }
}

Edit: as pointed out by @assylias, using unsynchronized / non volatile code is only guaranteed to work if there is only 1 read of hashCode because every consecutive read of that field could return 0 even though the first read could already see a different value. Above version fixes the problem.

Edit2: replaced with more obvious version, slightly less code but roughly equivalent in bytecode

public int hashCode() {
    int h = hashCode; // only read
    return h != 0 ? h : (hashCode = a + b);
    //                   ^- just a (racy) write to hashCode, no read
}
like image 20
zapl Avatar answered Dec 18 '22 07:12

zapl