4

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.
Kevin
  • 23,174
  • 26
  • 81
  • 111

4 Answers4

7

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...

assylias
  • 321,522
  • 82
  • 660
  • 783
  • Thanks, can you explain me over the second point and also one more question.. does immutable objects don't have multiple references... which makes them easier to for JVM to GC. – Kevin Sep 22 '13 at 22:10
  • 1
    @Kevin 2nd point: marking a class as final does not make it immutable, just not extendable. – herman Sep 22 '13 at 22:12
  • 2
    @Kevin Not sure what you mean, but immutable objects can hold multiple references and can be referred to by multiple other objects. Of course GC will only happen if there are no more references by (from any thread) reachable objects. – herman Sep 22 '13 at 22:13
  • You don't need threadsafety. If calculation happens concurrently it will result in the same value since hashCode must be deterministic. It's fine if you don't protect it and calculate it rarely twice. Unsafe = faster. – zapl Sep 22 '13 at 22:16
  • @herman: final class can't change so its immutable right? and more over String class is immutable and each time its creates a new String object rather than pointing to existing reference. – Kevin Sep 22 '13 at 22:17
  • @Kevin like I said, a final class just means no other class can extend it. If it has non-final variables and methods that mutate them, it's not immutable. – herman Sep 22 '13 at 22:18
  • 1
    @zapl it is more complicated than that - Igor's implementation could result in the hashcode being calculated several times as you point out - but more importantly, **it could also return null**. More about why it could happen, by one of the authors of the Java Memory Model: http://jeremymanson.blogspot.co.uk/2008/12/benign-data-races-in-java.html – assylias Sep 22 '13 at 22:23
  • @zapl and your implementation could erroneously return 0 too. – assylias Sep 22 '13 at 22:27
  • @assylias you sure? returning `(hashCode = a + b)` is no read of `hashCode` if I'm not mistaken. – zapl Sep 23 '13 at 10:02
  • @zapl I haven't checked the bytecode but it probably does `h=a+b; return h;` and `return h;` is a read. – assylias Sep 23 '13 at 10:05
  • @assylias http://pastebin.com/GTcep5VL it's returning the value from stack, no second read there. Not sure if that makes it safe :) – zapl Sep 23 '13 at 10:18
  • @zapl fair enough - however you could still have the condition return true and the second part of your ternary expression `? hashcode :` return 0. – assylias Sep 23 '13 at 10:22
  • @assylias That's what I suspected.. Thx, example fixed and something learned – zapl Sep 23 '13 at 11:02
4

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
}
zapl
  • 63,179
  • 10
  • 123
  • 154
  • What about when the hash code evaluates to 0? If it can, it's better to use an `Integer` and `null` to indicate uninitialized, or something similar. – Bernhard Barker Sep 22 '13 at 21:56
  • 1
    BTW `String` does this. – Peter Lawrey Sep 22 '13 at 21:57
  • @Dukeling The downside of creating an object is that it allocates an object and creates garbage which wouldn't be there otherwise. This defeats the point of trying to reduce the work the program does. – Peter Lawrey Sep 22 '13 at 21:58
  • @PeterLawrey except that this version is not that safe. – assylias Sep 22 '13 at 22:04
  • @PeterLawrey Well, it depends how often it's zero and how much work is done to recalculate it if it is. For this example it can easily be zero, but not much work is done, so it probably doesn't justify the object. Maybe a boolean to prevent the object overhead? – Bernhard Barker Sep 22 '13 at 22:07
  • 1
    @Dukeling I would prefer to use a `long` than use an `Integer` and use a value which is not possible for an `int` for unset. – Peter Lawrey Sep 22 '13 at 22:08
  • A `long` sounds like more work than a `boolean`, potentially more expensive comparison on x86 – zapl Sep 22 '13 at 22:11
  • I would use `int h=hashcode; if(h==0) { h=a+b; hashcode=h; } return h;` to err on the cautious side. – assylias Sep 23 '13 at 11:14
3

What that line means is, since the object is immutable, then the hashCode has to only be computed once. Further, it doesn't have to be computed when the object is constructed - it only has to be computed when the function is first called. If the object's hashCode is never used then it is never computed. So the hashCode function can look something like this:

@Override public int hashCode(){
    synchronized (this) {
        if (!this.computedHashCode) {
            this.hashCode = expensiveComputation();
            this.computedHashCode = true;
        }
    }
    return this.hashCode;
}
Claudiu
  • 224,032
  • 165
  • 485
  • 680
  • 1
    Your implementation is not thread safe (a thread could observe a value true for the boolean but the default 0 value for the hashcode) unless you take additional precautions. – assylias Sep 22 '13 at 21:55
  • 1
    @assylias The boolean is only set to true *after* the computation, so this can't happen, can it? (though it could call `expensiveComputation` again.) – Bernhard Barker Sep 22 '13 at 22:08
  • 1
    Without appropriate synchronisation, anything can happen in a multithreaded environment, including instructions reordering... – assylias Sep 22 '13 at 22:09
  • [Related question](http://stackoverflow.com/questions/12554570/instructions-reordering-in-java-jvm). – Bernhard Barker Sep 22 '13 at 22:30
1

And to add to other answers.

Immutable object cannot be changed. The final keyword works for basic data types such as int. But for custom objects it doesn't mean that - it has to be done internally in your implementation:

The following code would result in a compilation error, because you are trying to change a final reference/pointer to an object.

final MyClass m = new MyClass();
m = new MyClass();

However this code would work.

final MyClass m = new MyClass();
m.changeX();
Community
  • 1
  • 1
Kuba Spatny
  • 26,618
  • 9
  • 40
  • 63