4

I would like to have a map with the class type as a key in order to avoid casting when accessing the value.

Update
The following is actual code you can copy/paste in an IDEA and reproduce it.

    interface MyInterface {

    }

    class SomeConcreteClass implements MyInterface{

    }

    private Map<Class<?>, ? extends MyInterface> map = newMap();

    private Map<Class<?>, ? extends MyInterface> newMap() {
        Map<Class<?>, ? extends MyInterface> map = new HashMap<>();
        map.put(SomeConcreteClass.class, new SomeConcreteClass());
        return map;
    }

    private void accessMap() {
        SomeConcreteClass clazz = (SomeConcreteClass) map.get(SomeConcreteClass.class); <== I want to avoid cast here
    }

Problem: This does not compile. I get in this line:

map.put(SomeConcreteClass.class, new SomeConcreteClass());  

the error:

Wrong 2nd argument type. Found: 'SomeConcreteClass required '? extends MyInterface`

What am I doing wrong here? SomeConcreteClass should be accepted as it implements the interface

Jim
  • 3,845
  • 3
  • 22
  • 47
  • Please post an actual non-working example; you've omitted a number of important details such as access modifiers and class structure. – chrylis -cautiouslyoptimistic- Apr 07 '20 at 11:00
  • 1
    @chrylis-onstrike- that's an actual non-working example, just tested it out and Idea tells that Required type: capture of ? extends MyInterface Provided: SomeConcreteClass – Artsiom Apr 07 '20 at 11:14
  • @chrylis-onstrike-: That code in the post, if you copy/paste it in an IDEA you can reproduce the issue. Thank you Artsiom – Jim Apr 07 '20 at 11:29

1 Answers1

1

This can be simplified, a lot:

Map<String, ? extends CharSequence> map = new HashMap<>();
map.put("", ""); // <-- will not compile

List<? extends CharSequence> l = new ArrayList<>();
l.add(""); // <-- will not compile

The principle behind this is called PECS (very famous). Why this is not allowed is a bit verbose to read, but mainly explained here. Though not obvious, if that would be allowed - it might cause problems in other places, the answer I linked explains that.

You can achieve what you want with a so-called typesafe heterogeneous container:

static class TypeSafeValue {

    private MyInterface t;

    TypeSafeValue() {
    }

    public <T> TypeSafeValue setValue(MyInterface t) {
        this.t = t;
        return this;
    }

    public <T> T getValue(Class<T> clazz) {
        return clazz.cast(t);
    }
}

And usage would be:

private Map<Class<?>, TypeSafeValue> newMap() {
    Map<Class<?>, TypeSafeValue> map = new HashMap<>();
    map.put(SomeConcreteClass.class, new TypeSafeValue().setValue(new SomeConcreteClass()));
    return map;
}

private void accessMap() {
    SomeConcreteClass clazz = map.get(SomeConcreteClass.class).getValue(SomeConcreteClass.class);
}
Eugene
  • 117,005
  • 15
  • 201
  • 306
  • This is helpful indeed. By the way is there a solution to what I was trying to do there? I.e. avoid the cast? – Jim Apr 07 '20 at 14:34
  • @Jim I wanted to actually ask you, why do you think you need `? extends MyInterface` part? you do know you can declare it as `private Map, MyInterface> map = ...`, right? what is the actual benefit of introducing a bounded type in the `Map` declaration? – Eugene Apr 07 '20 at 14:36