I decided to write some common Higher Order Functions in Java (map, filter, reduce, etc.) that are type safe via generics, and I'm having problems with wildcards matching in one particular function.
Just to be complete, the functor interface is this:
/**
* The interface containing the method used to map a sequence into another.
* @param <S> The type of the elements in the source sequence.
* @param <R> The type of the elements in the destination sequence.
*/
public interface Transformation<S, R> {
/**
* The method that will be used in map.
* @param sourceObject An element from the source sequence.
* @return The element in the destination sequence.
*/
public R apply(S sourceObject);
}
The troubling function is like a map, but instead of transforming a Collection it transforms a Map (at first I thought it should be called mapMap
, but it sounded so stupid that I ended up calling it remapEntries
).
My first version was (and take a sit, because the signature is quite a monster):
/**
* <p>
* Fills a map with the results of applying a mapping function to
* a source map.
* </p>
* Considerations:
* <ul>
* <li>The result map must be non-null, and it's the same object what is returned
* (to allow passing an unnamed new Map as argument).</li>
* <li>If the result map already contained some elements, those won't
* be cleared first.</li>
* <li>If various elements have the same key, only the last entry given the
* source iteration order will be present in the resulting map (it will
* overwrite the previous ones).</li>
* </ul>
*
* @param <SK> Type of the source keys.
* @param <SV> Type of the source values.
* @param <RK> Type of the result keys.
* @param <RV> Type of the result values.
* @param <MapRes>
* @param f The object that will be used to remapEntries.
* @param source The map with the source entries.
* @param result The map where the resulting entries will be put.
* @return the result map, containing the transformed entries.
*/
public static <SK, SV, RK, RV, MapRes extends Map<RK, RV>> MapRes remapEntries(final Transformation<Map.Entry<SK, SV>, Map.Entry<RK,RV>> f, final Map<SK, SV> source, MapRes result) {
for (Map.Entry<SK, SV> entry : source.entrySet()) {
Map.Entry<RK, RV> res = f.apply(entry);
result.put(res.getKey(), res.getValue());
}
return result;
}
And it seems to be quite correct, but the problem is that the transformation used must match exactly the type parameters, making difficult to reuse map functions for types that are compatible. So I decided to add wildcards to the signature, and it ended up like this:
public static <SK, SV, RK, RV, MapRes extends Map<RK, RV>> MapRes remapEntries(final Transformation<? super Map.Entry<? super SK, ? super SV>, ? extends Map.Entry<? extends RK, ? extends RV>> f, final Map<SK, SV> source, MapRes result) {
for (Map.Entry<SK, SV> entry : source.entrySet()) {
Map.Entry<? extends RK, ? extends RV> res = f.apply(entry);
result.put(res.getKey(), res.getValue());
}
return result;
}
But when I'm trying to test it, wildcard matching fails:
@Test
public void testRemapEntries() {
Map<String, Integer> things = new HashMap<String, Integer>();
things.put("1", 1);
things.put("2", 2);
things.put("3", 3);
Transformation<Map.Entry<String, Number>, Map.Entry<Integer, String>> swap = new Transformation<Entry<String, Number>, Entry<Integer, String>>() {
public Entry<Integer, String> apply(Entry<String, Number> sourceObject) {
return new Pair<Integer, String>(sourceObject.getValue().intValue(), sourceObject.getKey()); //this is just a default implementation of a Map.Entry
}
};
Map<Integer, String> expected = new HashMap<Integer, String>();
expected.put(1, "1");
expected.put(2, "2");
expected.put(3, "3");
Map<Integer, String> result = IterUtil.remapEntries(swap, things, new HashMap<Integer, String>());
assertEquals(expected, result);
}
The error is:
method remapEntries in class IterUtil cannot be applied to given types
required: Transformation<? super java.util.Map.Entry<? super SK,? super SV>,? extends java.util.Map.Entry<? extends RK,? extends RV>>,java.util.Map<SK,SV>,MapRes
found: Transformation<java.util.Map.Entry<java.lang.String,java.lang.Number>,java.util.Map.Entry<java.lang.Integer,java.lang.String>>,java.util.Map<java.lang.String,java.lang.Integer>,java.util.HashMap<java.lang.Integer,java.lang.String>
So, any hints on how to fix this? Or should I give up and write explicit loops for this? ^_^