HOAB

History of a bug

Back to basics : hashcode and hashmap

Rédigé par gorki Aucun commentaire

Problem :

I'm building a Java agent to monitor objects. I track some objects and get metrics ont it. But to have less impact on memory, if objects are removed, I don't want to keep a reference to them. It could prevent the garbage collector to remove them.

Objects can be scanned multiple time for some reasons, so to keep a unique reference, I built an hashmap to track only one reference of these objects.

A hashmap with weak reference.

As stated by this article, weak reference can not be compared regarding their reference but only between WeakReference objects themselves. So I used the proposal of this article and create a WeakEqualReference object.

Adding is working well.

But after a while, I can not remove an object : I go through the map, I tried to remove an object given by the entrySet iterator : impossible.

Solution :

Well, I search for a while, and here is a summary.

In the article, hashcode used for WeakEqualReference is :

	@Override
	public int hashCode() {
		T value = this.get();
		return value != null ? value.hashCode() : super.hashCode();
	}

In fact, it gives :

  1. on the first call : the hashcode of the value
  2. on the second call, after value has been removed : the hashcode of the WeakEqualsReference

But when object is stored in the hashmap, this is the first hashcode which is used. And the hashmap never find the new hashcode after the value becomes null. Containskey, remove, all is wrong.

Hashcode must be consistent for the duration of the WeakEqualsReference.

So, my solution is to compute the hascode on the first call, when the object is stored in the hashmap, return it each times after that. WeakEqualsReference can now be removed safely.

import java.lang.ref.WeakReference;

public class WeakEqualReference<T> extends WeakReference<T> {

	private int hashCode = 0;

	public WeakEqualReference(T r) {
		super(r);
	}

	@SuppressWarnings("unchecked")
	@Override
	public boolean equals(Object other) {

		boolean returnValue = super.equals(other);

		// If we're not equal, then check equality using referenced objects
		if (!returnValue && (other instanceof WeakEqualReference<?>)) {
			T value = this.get();
			if (null != value) {
				T otherValue = ((WeakEqualReference<T>) other).get();

				// The delegate equals should handle otherValue == null
				returnValue = value.equals(otherValue);
			}
		}

		return returnValue;
	}

	@Override
	public int hashCode() {
		if (hashCode == 0) {
			T value = this.get();
			hashCode = value != null ? value.hashCode() : super.hashCode();
		}
		return hashCode;
	}
}

And for the story :

- one of my values referenced was a bean with a String and another instance of WeakEqualsReference. I still had issue, but due to the String ! This string value was updated so hashcode before and after was different. Same cause, same effects.

Here the example was complicated by the WeakReference usage, but with a simple HashSet :


import java.util.HashSet;
import java.util.Objects;

public class HashCodeTest {

    public static class HashTestValue {
        public String value;

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            HashTestValue that = (HashTestValue) o;
            return Objects.equals(value, that.value);
        }

        @Override
        public int hashCode() {
            return Objects.hash(value);
        }
    }


    public static void main(String... args) {

        HashSet<HashTestValue> set = new HashSet();

        HashTestValue mapTestValue = new HashTestValue();
        mapTestValue.value = "1234";
        set.add(mapTestValue);

        System.out.println("set.contains(mapTestValue) = " + set.contains(mapTestValue));

        mapTestValue.value = "5678";

        System.out.println("set.contains(mapTestValue) = " + set.contains(mapTestValue));

    }
}

Conclusion : overriding equals and hascode is OK, but be careful of the consistency if you need to test the object after the insert !

 

Fil RSS des articles