Wednesday, November 15, 2017

Why you should be careful when using Lombok or other code generation tools

Look at the following code:



Will the testHashCode succeed or fail?

The answer it will fail, since the second assert: assertTrue(set.contains(myEntity)) will not find myEntity in the HashSet.
But it's there, right? It was never removed.

So what we have here is: 1. Business problem, since it's impossible to get object from set that is there. 2. Memory leak.

But how did it happen?

The problem is with Lombok's @Data annotation. It's a very convenient annotation that auto-generates all methods the java utility methods: equals, hashCode, toString as well as relevant constructors and getters.
Yes, it's very convenient, but a hidden problem is introduced: equals and hashCode include all fields and when value of a field changes, the hashCode returns a different value. Therefore, the object cannot be found in HashSet anymore.

This problem is not unique to Lombok. Exactly the same problem will occur if you write the method yourself by using mutable fields or if you use any other code-generation or reflection tools.
However, if you write code yourself, it's a bit more visible, while with Lombok it's kind of woodoo.

The best practices here are not related to Lombok or other library and are quite simple:
1. As much as possible try to make your class immutable.
2. Even if a class is mutable, are all fields mutable? Use only immutable fields in hashCode and equals and you will be safe.
3. If a class is completely mutable and you cannot rely on some immutable fields, reconsider if you need to override hashCode and equals at all. Is default implementation sufficient?
4. If none of above doesn't work for you - document. Put a HUGE WARNING in javadoc explaining why the users of the class must be careful, if they decide to store the instances in HashSet or as a key in a HashMap.

No comments: