2

Look at this short class.

What does it do? It is a HashMap where the key is a Class object and the value is a Hashmap with key and value of the type which is defined by the Class object.

Say you want to have a method add(K obj), that will be a convenience method. Why would you force a user to type add(Person.class, personInstance), when you can also let him just pass you the instance itself - the code should be able to take the class of the object and put the instance under the key being the class of the object. But I can't. The add(K obj) convenience method does not compile:

public class TypedHashmap extends HashMap<Class, HashMap> {

    public <K> HashMap<K, K> getHashMap(Class<K> type) {
        return (HashMap<K, K>) getOrDefault(type, new HashMap<K, K>());
    }

    public <K> void add(K obj, Class<K> type) {
        HashMap<K, K> toModify = getHashMap(type);
        toModify.put(obj, obj);
    }

    public <K> void add(K obj) {
        add(obj, obj.getClass());
    }
}

The compile error:

enter image description here

I can see that Java is not sure about obj.getClass() type. But I do not see why. Is it because it cannot ensure that the class is not actually something extending the K? What is the problem here? Can this be somehow worked around?

Ev0oD
  • 1,395
  • 16
  • 33
  • The root problem is the return type of `Object.getClass()` – ZhongYu May 22 '15 at 22:59
  • Do you really want a map of maps? Or a map of classes to an instance of that class? – erickson May 22 '15 at 23:03
  • The reasoning is difficult. I initally wanted a map of , Set>. However, you cannot fetch a specific object from a set, you can only ask if it is there or to remove it. See problems that come with it here http://stackoverflow.com/a/18380755/1920149 and the suggestion that you should use a hashmap as a replacement for a Set here: http://stackoverflow.com/questions/7283338/getting-an-element-from-a-set#comment37586270_18380755 – Ev0oD May 22 '15 at 23:07
  • 1
    If you are going to do this, don't extend `HashMap`. Extending `HashMap` is a design with all kinds of holes in it. Somebody can call `put` with any kind of map they want. It's more proper to write a class that wraps around the map. – Radiodef May 23 '15 at 01:25

3 Answers3

3

Generics are a tool to ensure type safety.

The way you have designed your API, it is not type safe to this level, which is why you get this error.

public <K> void add(K obj) {
    add(obj, (Class<K>) obj.getClass());
}

Is the way to make it explicit that you cannot ensure type saftey here. Or just do a

public void add(Object obj) {
    add(obj, (Class<Object>) obj.getClass());
}

Which will give you the same level of type safety (none).

If you want a slightly cleaner solution (no cast in the simpler add function), you'll have to modify your other add function, too.

public <K> void add(K obj, Class<? super K> type) {
    HashMap<K, K> toModify = getHashMap(type);
    toModify.put(obj, obj);
}

public void add(Object obj) {
    add(obj, obj.getClass());
}

But in the end I believe you are implementing an anti-pattern.

The problem is this:

Number a = new Integer(3);
map.add(a);
map.getHashMap(Number.class).contains(a);

yields false. The Integer is not in the Number map. Because your maps do not support subtypes and inheritance.

adds an Integer object to the list. But unless I know which

Has QUIT--Anony-Mousse
  • 76,138
  • 12
  • 138
  • 194
  • Might be true. It felt kind of hacky and not right. But I wanted to have a one return object, which could be filled up with a lot of objects of a lot of types without necessarily implementing a collection for each type within the object - that would not be flexible enough and it would be a lot of unnecessary coding. Still do not know how to work it out better. But that is probably a different question. – Ev0oD May 22 '15 at 22:52
  • You can use a collection of `Object` if you want to store objects of different types. It's fairly straightforward how to add a getter with an additional type check. (Still probably an antipattern. Don't fake type safety where you opted to not have it...) – Has QUIT--Anony-Mousse May 22 '15 at 22:56
  • Omg, what a great simple solution! Thank you. So I would just for example create an iterator and iterate skipping any object that is not matching the type I am currently looking for, right? – Ev0oD May 22 '15 at 22:59
  • When performing the type check you might use [`Class.isAssignableFrom(Class)`](http://docs.oracle.com/javase/7/docs/api/java/lang/Class.html#isAssignableFrom%28java.lang.Class%29) which returns *the boolean value indicating whether objects of the type cls can be assigned to objects of this class*. – Elliott Frisch May 22 '15 at 23:16
1

As far as the compiler is concerned obj.getClass() returns an instance of type Class<?> and you don't have a method with signature

void add(K obj, Class<?> type)

You have

void add(K obj, Class<K> type)

which is close, but not exactly right.

Asaph
  • 159,146
  • 25
  • 197
  • 199
  • Okay, but if I change it to Class> it does not help me at all, because then I receive on the line getHashmap a Hashmap,?> to which I cannot insert K type objects as keys nor values. – Ev0oD May 22 '15 at 22:33
  • Try this: `add(obj, (Class) obj.getClass());` Ugly for sure, but will make the compiler stop complaining. – Asaph May 22 '15 at 22:35
1

You didn't make your class generic, so all of the method generics are independent (each K is a different ANY). I think you wanted something like

public class TypedHashmap<K> extends HashMap<Class<K>, HashMap<K, Class<K>>> {
    public HashMap<K, Class<K>> getHashMap(Class<K> type) {
        return (HashMap<K, Class<K>>) getOrDefault(type, new HashMap<>());
    }

    public void add(K obj, Class<K> type) {
        HashMap<K, Class<K>> toModify = getHashMap(type);
        toModify.put(obj, type);
    }

    public void add(K obj) {
        add(obj, (Class<K>) obj.getClass());
    }
}
Elliott Frisch
  • 198,278
  • 20
  • 158
  • 249
  • Thanks. But actually, it is a HashMap, HashMap>. However I tried out your thing but with this modification so that the value is also K. It allowed me to create the method, but however turns .entrySet() method into one that returns Set instead of Set. There is some classcasting necessary later, so that I can access key value pairs. I do not clearly see the reason for that. – Ev0oD May 22 '15 at 22:57