Margrethe
Margrethe

Reputation: 31

How to use an Interface as Map’s Key

I am looking for help on the subject how to use an Interface as Maps Key. I tried to implement a solution, and get no compiletime errors but runtime errors when running my integration tests. Is it not possible to use an Interface as a Key, or is it my tests there is something wrong with?

My code looks something like this

private Map<AInterface, Values> myMap = new HashMap<AInterface, Values>();

Upon retreiving the set of keys from myMap they do contain objects with expected Id, but are compared to not-equal. So when using myMap.get(Object key) i get null, eventhough an object with the same id is there. When using the concrete class instead of the interface all tests pass:

private Map<AClass, Values> myMap = new HashMap<AClass, Values>();

I've read Generics where it states that for a Map, you are required to replace the type variables K and V by concrete types that are subtypes of Object.

Since the compiler does not give me any warnings when using an Interface for K, my guess would have been that the tests have errors.

Does anybody have any experience with using Interfaces as Key in a Map? And could give me any hints on what I am doing wrong?

Upvotes: 3

Views: 13572

Answers (6)

Andreas Dolk
Andreas Dolk

Reputation: 114817

[...] it states that for a Map, you are required to replace the type variables K and V by concrete types that are subtypes of Object.

I was close to asking a question at SO but I think I've got the answer.

concrete type - It sounds a bit like 'concrete class', as if interfaces or abstract classes are not allowed. But it just says, that your not allowed to replace 'K' by another generic type 'S' or so. It has to be a 'real' type: interfaces, classes, even enums are concrete enough.

subtypes of Object - Again it sounds like interfaces are not allowed because they don't subclass Object. Yes, but: you can't instantiate an interface anyway, so the real objects that are put into the map are always class instances. The only class of Java types that does not subclass Object are Java primitives , including arrays of Java primitives. So Map<int, String> is not allowed as well as Map<String, int[]>.

Upvotes: 0

Dean J
Dean J

Reputation: 40359

If you're using Apache Commons lang JAR, perhaps add this to your class.

public int hashCode() { 
    return HashCodeBuilder.reflectionHashCode(this);
}

Upvotes: -1

David Roussel
David Roussel

Reputation: 5916

The problem is nothing to do with interfaces. The generic types are erased at compilation, and at run-time HashMap only deals with Object instances.

We've no way of knowing what your problem is as you don't show any code, but it's most likely that your keys are mutable and your hashCode is changing.

For instance:

class MyKey {
    String name;

    public int hashCode() {
        return name.hashCode();
    }

    // assume suitable equals() implementation
}

Map<MyKey,Integer> myMap = new HashMap();

MyKey key1 = new MyKey();
key1.name = "Jimmy";

myMap.put(key1, 10);

key1.name = "Johnny";

myMap.get(key1);  // return null

If the hashcode of an object changes between it being added to a map, and trying to retrieve it, then the HashMap will (probably) look in the wrong hash bucket, and not find any value. You might sometimes get a result, if the old hashcode and the new hashcode resolve to the same hash bucket.

Upvotes: 0

Andreas Dolk
Andreas Dolk

Reputation: 114817

Both examples should work perfectly. It's OK to use interfaces, abstract or concrete class types in the 'generics'. I often use the List interface in Maps and never had problems.

You say you only have to change the type of myMap and the constructor to make your tests pass? What type of objects do you use as keys in the map, AClass or something else?

Have a close look at your runtime errors or provide us some details (just the exceptions w/o stacktraces).

For the other problem, as Wesho already answered, implement either both hashcode and equals or (for testing) none of them on the class that you really use for keys in the map.

EDIT

Knowing from a comment, that hashcode and equals are implemented: there's one possible trap - if you change the UUID after the object has already been put to the map (as a key), then you may not be able to find your values afterwards (although a rehash on the map should make it work again).

EDIT 2

If you receive a NPE directly on myMap.get(AClass key), then either myMap is null or the key (but that still doesn't solve the other mystery...)

EDIT 3

Just checked the implementation of hashcode and equals on UUID and that's ok. The calculation is limited to the 128 bit UUID only. So if you create two UUID objects for the same UUID value and test for equality, then the two UUID objects are not the same but equal. That's good. If you have a getter for UUID in AClass, then you experiment with a Map like HashMap<UUID, Values> myMap and check if it still works (Maybe, by chance, the code change unhides the real bug ;) )

Upvotes: 0

vahidg
vahidg

Reputation: 3963

The objects that are extending your interface should all implement both hashCode and equals. If equals returns true but the hashCode values are not equal, then the appropriate object is not found since the JVM places the objects in 'buckets' (when storing in a Map) according to their hashCode value.

Upvotes: 2

pmf
pmf

Reputation: 7759

Your classes must implement hashCode and equals (explanation; you should also familiarize yourself with the contract of the Map-interface).

Upvotes: 5

Related Questions