7

Hope somebody can help me out of this confussion.

I made this method:

public static <T> void myMethod(Map<Class<T>, MyInterface<T>> map) {
}

Used paramter T in order to make sure that the class used as key is the same as the class used as parameter in MyInterface.

Now I want to pass a map which different classes as keys, of course, and corresponding implementations of MyInterface.

But it doesn't work, getting syntax errors because of type parameters. Here is the code, I hope is self explanatory.

    import java.util.HashMap;
    import java.util.Map;

    public class Test {

    public static void main(String[] args) {
        Map<Class<?>, MyInterface<?>> map = new HashMap<Class<?>, MyInterface<?>>();

    //      Map<Class<Object>, MyInterface<Object>> map = new HashMap<Class<Object>, MyInterface<Object>>();

        map.put(Object.class, new MyObjectImpl());

        //if I use Map<Class<Object>, MyInterface<Object>> I get a compiler error here
        //because map<String> is not map<Object> basically
        map.put(String.class, new MyStringImpl());

        //this would be possible using <?>, which is exactly what I don't want
    //      map.put(String.class, new MyIntegerImpl());

        //<?> generates anyways a compiler error
        myMethod(map);
    }

    //use T to make sure the class used as key is the same as the class of the parameter "object" in doSomething  
    public static <T> void myMethod(Map<Class<T>, MyInterface<T>> map) {

    }

    interface MyInterface<T> {
        void doSomething(T object);
    }

    static class MyObjectImpl implements MyInterface<Object> {
        @Override
        public void doSomething(Object object) {
            System.out.println("MyObjectImpl doSomething");
        }
    }

    static class MyStringImpl implements MyInterface<String> {
        @Override
        public void doSomething(String object) {
            System.out.println("MyStringImpl doSomething");
        }
    }

    static class MyIntegerImpl implements MyInterface<Integer> {
        @Override
        public void doSomething(Integer object) {
            System.out.println("MyIntegerImpl doSomething");
        }
    }
}
7
  • Class names ending in Impl are usually symptoms of over-engineering (e.g. it implies there is only one implementation of a given interface, which can not be enforced). Commented May 21, 2012 at 12:04
  • 1
    Forget about it, this can't work. Generics can only define a homogenous map. You can't enforce anything on an entry-by-entry basis, only the same thing for all map entries. Commented May 21, 2012 at 12:13
  • @Romain well, the actual names are not like that, just wrote it to make a clear example. But your comment could apply as well. What do you mean which it implies only 1 implementation, I have 3? Commented May 21, 2012 at 12:21
  • You only have one, really - their code is virtually identical, only the name changes. All you're doing there is aliasing in practice. MyStringImpl == MyGenericImpl<String>. Commented May 21, 2012 at 12:26
  • 1
    @Romain what you are saying is actually quite untrue. Usually, people put interfaces to allow to have multiple implementation. Often, developers tend to append 'Impl' for the default implementation. In this case, although the code could be factorized, there is 3 very different implementations, with different concrete class definitions. Commented May 21, 2012 at 12:44

4 Answers 4

8

You can't do that, because there is no constraint defined in Map's put() method between the key and the value. If you want to assure that your map is populated properly (i.e. create such constraint), hide the map behind some API that will check the correctness, for example:

public <T> void registerInterface(Class<T> clazz, MyInterface<T> intf) {
    map.put(clazz, intf);
}

Then, just call the registerInterface instead of manually populating the map.

Sign up to request clarification or add additional context in comments.

1 Comment

map.put(clazz, intf) I think :)
3

As far as I know, you cannot declare a Map like you describe in Java. All you can do is performing type checking and/or add constraints.

Guava offers something that approaches your problem with ClassToInstanceMap. So one way to do this would be to use MapConstraints.constrainedMap (like the example below)

import java.text.ParseException;
import java.util.HashMap;
import java.util.Map;

import com.google.common.collect.MapConstraint;
import com.google.common.collect.MapConstraints;

public class Main {

    interface MyInterface<T> {
        void doSomething(T object);

        Class<T> getType();
    }

    static class MyObjectImpl implements MyInterface<Object> {
        @Override
        public void doSomething(Object object) {
            System.out.println("MyObjectImpl doSomething");
        }

        @Override
        public Class<Object> getType() {
            return Object.class;
        }
    }

    static class MyStringImpl implements MyInterface<String> {
        @Override
        public void doSomething(String object) {
            System.out.println("MyStringImpl doSomething");
        }

        @Override
        public Class<String> getType() {
            return String.class;
        }
    }

    static class MyIntegerImpl implements MyInterface<Integer> {
        @Override
        public void doSomething(Integer object) {
            System.out.println("MyIntegerImpl doSomething");
        }

        @Override
        public Class<Integer> getType() {
            return Integer.class;
        }
    }

    public static void main(String[] args) throws ParseException {

        Map<Class<?>, MyInterface<?>> map = MapConstraints.constrainedMap(new HashMap<Class<?>, Main.MyInterface<?>>(),
                new MapConstraint<Class<?>, MyInterface<?>>() {
                    @Override
                    public void checkKeyValue(Class<?> key, MyInterface<?> value) {
                        if (value == null) {
                            throw new NullPointerException("value cannot be null");
                        }
                        if (value.getType() != key) {
                            throw new IllegalArgumentException("Value is not of the correct type");
                        }
                    }
                });
        map.put(Integer.class, new MyIntegerImpl());
        map.put(String.class, new MyStringImpl());
        map.put(Object.class, new MyObjectImpl());
        map.put(Float.class, new MyIntegerImpl()); //<-- Here you will get an exception
    }
}

1 Comment

+1 because very informative but the other answer is more generic, in my current opinion.
0

I do not think this is possible :

Class<T> only ever accepts T.class as value. Class<Object> will not accept String.class, even though Object is a superclass of String.

For this reason any map with Class<T> as key can have only one element, with T.class as key value, whatever the value of T.

The compiler will only ever accept a map with a definite value of T as parameter. You cannot write Map<Class<?>, MyInterface<?>> because each ? is assumed to be different : it does not match Map<Class<T>, MyInterface<T>> which requires T to have the same value.

That said, myMethod will only ever accept single-entry maps, which does not seem useful.

Comments

0

Change your method signature to

public static <T> void myMethod(Map<Class<? extends T>, MyInterface<? extends T>> map) {

}

now your declaration and invocation should work..

Map<Class<?>, MyInterface<?>> map = new HashMap<Class<?>, MyInterface<?>>();
    map.put(Integer.class, new MyIntegerImpl());
    map.put(String.class, new MyStringImpl());
    map.put(Object.class, new MyObjectImpl());
    myMethod(map);

1 Comment

But this map still allows you to do map.put(Integer.class, MyStringImpl()); which is exactly what is NOT asked in the question. Btw, the OP already suggested that in his question.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.