views:

546

answers:

4

I'm wanting to write a method that I can use to initialise a Map. First cut:

Map map(Object ... o) {for (int i = 0; i < o.length; i+=2){result.put(o[i], o[i+1])}}

Simple, but not type-safe. Using generics, maybe something like:

<TKey, TValue> HashMap<TKey, TValue> map(TKey ... keys, TValue ... values)

but that syntax isn't supported. So eventually I come to this:

public static <TKey, TValue, TMap extends Map<? super TKey, ? super TValue>> TMap map(TMap map, Pair<? extends TKey, ? extends TValue> ... pairs) {
    for (Pair<? extends TKey, ? extends TValue> pair: pairs) {
        map.put(pair.getKey(), pair.getValue());
    }
    return map;
}

public static <TKey, TValue> HashMap<? super TKey, ? super TValue> map(Pair<? extends TKey, ? extends TValue> ... pairs) {
    return map(new HashMap<TKey, TValue>(), pairs);
}

public static <TKey, TValue> Pair<TKey, TValue> pair(TKey key, TValue value) {
    return new Pair<TKey, TValue>(key, value);
}

public static final class Pair<TKey, TValue> {
    private final TKey key;
    private final TValue value;
    Pair(TKey key, TValue value) {this.key = key; this.value = value; }
    public TKey getKey() {return key;}
    public TValue getValue() {return value;}
}

But when I try it out, I need to cast it:

private static final Map<? extends Class<? extends Serializable>, ? super TypeHandler<? extends Serializable > > validCodeTypes =
    /* (Map<? extends Class<? extends Serializable>, ? super TypeHandler<? extends Serializable >>) */
 map(
    pair(Integer.class,   new IntHandler()),
    pair(Integer.TYPE,    new IntHandler()),
    pair(Character.class, new CharHandler()),
    pair(Character.TYPE,  new CharHandler()),
    pair(String.class,    new StringHandler())
);

private interface TypeHandler<TType extends Serializable> {}

private static class CharHandler implements TypeHandler<Character> {}
private static class IntHandler implements TypeHandler<Integer> {}
private static class StringHandler implements TypeHandler<String> {}

Can anyone tell me how to code my map() methods so that it is entirely general yet doesn't need to be casted?

+3  A: 

To make life easier for yourself, never use a return type that contains wildcards. Wildcard types, in general, are for method parameters only.

So, try this:

public static <TKey, TValue, TMap extends Map<TKey, TValue>> TMap map(TMap map, Pair<? extends TKey, ? extends TValue>... pairs) {
    for (Pair<? extends TKey, ? extends TValue> pair: pairs) {
        map.put(pair.getKey(), pair.getValue());
    }
    return map;
}

public static <TKey, TValue> HashMap<TKey, TValue> map(Pair<? extends TKey, ? extends TValue>... pairs) {
    return map(new HashMap<TKey, TValue>(), pairs);
}

I haven't tested it, but give it a go and see how you fare.

P.S., rather than using a made-up Pair type, you may find it easier to use Map.Entry.

Chris Jester-Young
A: 

Thanks, that did the trick for that map() method. How would I use a Map.Entry without creating a new class?

But the other map(Map, Pairs ...) method doesn't work for such a complicated case

Hmm, I see your point: Map.Entry is an interface. My bad. Well, I suppose you can make your Pair class implement Map.Entry then, and have your methods take Map.Entry as input parameters. :-)
Chris Jester-Young
Remember that you can make all the setters throw UnsupportedOperationException if you want them to be read-only.
Chris Jester-Young
+1  A: 

Why not this? Did I misunderstand something?

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

public class ToHash {
    public static <K, V> Map<K, V> toHash(Object... objects) {
     Map<K, V> map = new HashMap<K, V>(objects.length / 2);
     if (objects.length % 2 != 0) {
      throw new IllegalArgumentException("Odd number of elements: " + objects.length);
     }
     for (int i = 0; i < objects.length; i += 2) {
      map.put((K) objects[i], (V) objects[i + 1]);
     }
     return map;
    }
}
Pål GD
Test case: http://pastebin.com/f9e1eea3
Pål GD
Without warnings: http://pastebin.com/f2d59bc93Yields the result: 1=one 2=two 3=three
Pål GD
It "works", but you lose compile-time type safety. Obviously, the OP went to great lengths to make their code compile-time type-safe.
Chris Jester-Young
Yes, but that's too bad. He wont get it by using varargs, unless key and value are of the same type.
Pål GD
A: 

pgdx: Your technique certainly works but it does not prevent me from saying something like:

Map<Long, Date> map = toHash("hello", "world");

I was looking for a way which would allow the compiler to pick up on any type-mismatch errors.

Adrian Pronk