开发者

How do I make Java to infer the right generic map type for my typesafe map literals?

I am trying to write a typesafe map literal helper class in java that can generate maps of any type and have the compiler check that all given keys and values conform to the type of the map. For a map with 开发者_如何学运维two values there would be a function with the following signature:

public static <KEY, VALUE> Map<KEY, VALUE> make(final KEY key1,
        final VALUE value1,
        final KEY key2,
        final VALUE value2)

Now I would have expected that something like this would be possible:

    Map<Integer, Object> map = make(1, "bla", 3, 17);

However I get an compiler error:

Type mismatch: cannot convert from Map<Integer,Object&Serializable&Comparable<?>> to 
 Map<Integer,Object>

Is there a way to fix this? Of course, defining a function with signature make(Object... keysAndValues) would work, but I would loose the compile time typesafety. :-(


The error is probably here:

Map<Integer, Object> map = make(1, "bla", 3, 17);
                                   ^^^^^     ^^

VALUE > Object can't be inferred from both String and Integer

Try any of these:

// Helping the compiler to infer Object for VALUE
Map<Integer, Object> map = make(1, (Object)"bla", 3, 17);

// Explicitly bind VALUE to Object
Map<Integer, Object> map = MyClass.<Integer, Object>make(1, "bla", 3, 17);

// If you don't need to write into "map" (as user pmnt also suggested)
Map<Integer, ?> map = make(1, "bla", 3, 17);


Your code will work if you modify the Map's signature in the calling method:

No changes required to make

public static <A, B> Map<A, B> make(final A key1, final B value1,
        final A key2, final B value2) {
    Map<A, B> map = new HashMap<A, B>();
    map.put(key1, value1);
    map.put(key2, value2);
    return map;
}

Make's caller

You must change Map<String, Object> to Map<String, ? extends Object>:

public static void main(String[] args) {

    @SuppressWarnings("unused")
    Map<String, ? extends Object> m = make("a", new Integer(1), "2", "efg");

}

EDIT1: OpenJDK 1.6 + Eclipse Indigo compiler

EDIT2: When building generic maps like that, you must accept that you have to downcast when it comes to retrieving the values.

EDIT3: Is there a way to fix this? Of course, defining a function with signature make(Object... keysAndValues) would work, but I would loose the compile time typesafety.

You will always loose compile time safety at a certain point. At least when it comes to retrieval.


This does compile:

Map<Integer, ? extends Serializable> map = make(1, "bla", 3, 17);

This is what Java's generic type inference is giving you. Not what you expected?

You have a couple of options, either use a clarifier on the method:

Map<Integer, Object> map = Util.<Integer, Object>make(1, "bla", 3, 17);

Or you can use objects that don't have a common parent interface.

Map<Integer, Object> map = make(1, "bla", 3, new Object());


The compiler has to "guess" the types KEY and VALUE from the parameters given (the types of your Map where you store the return value are ignored). The "guess" for VALUE is the strictest type fitting to both Integer and String: anything that implements Comparable and Serializable (Object&Serializable&Comparable<?>).

You have two options: casting at least one VALUE to Object:

Map<Integer, Object> map = make(1, (Object)"bla", 3, 17);

or using a wildcard in your map

Map<Integer, ?> map = make(1, "bla", 3, 17);


String actually implements both Serializable and Comparable interface and Object does't.. so its saying Object does't implement but Its implicict typeCasted String does...


The following solution looks like it should work, but strangely it does only work in the Eclipse compiler, but not on a plain Java 6 compiler. 8-{ Why???

The method declaration is annoyingly complicated but allows the simplest syntax when creating maps:

public static <KEY, AKEY extends KEY, VALUE, AVALUE extends VALUE> Map<KEY, VALUE> make2(final AKEY key1,
        final AVALUE value1,
        final AKEY key2,
        final AVALUE value2)

Here, the compiler infers from the context in

Map<Integer, Object> map = make(1, "bla", 3, 17);

that KEY should be Integer and VALUE should be Object; AKEY is inferred from the given parameters as Integer and AVALUE as Object&Serializable&Comparable , and the compiler can verify that AKEY conforms to KEY and AVALUE to VALUE.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜