I made a method with type arguments, returning a generic type using these type arguments, and accepting Function arguments, which also depend on the type arguments. When I use lambdas as arguments, the compiler forces me to specify arguments like the method that seems wrong.
I am developing a utility class with methods for use with Stream.flatMap . It displays each type of collection entry in FlatEntry, which contains a key and value element, and can do this at several levels using the builder. flatEntryMapperBuilder method. Here is the code:
import java.util.function.Function; import java.util.stream.Stream; public class GdkStreams { public static <T, K, V> Function<T, Stream<FlatEntry<K, V>>> flatEntryMapper(Function<T, K> keyMapper, Function<T, Stream<V>> valueMapper) { return input -> { K key = keyMapper.apply(input); return valueMapper.apply(input).map(value -> new FlatEntry<>(key, value)); }; } public static <T, K, V> FlatEntryMapperBuilder<T, K, V> flatEntryMapperBuilder(Function<T, K> keyMapper, Function<T, Stream<V>> valueMapper) { return new FlatEntryMapperBuilder<>(keyMapper, valueMapper); } public static class FlatEntryMapperBuilder<T, K, V> { private Function<T, K> keyMapper; private Function<T, Stream<V>> valueMapper; private FlatEntryMapperBuilder (Function<T, K> keyMapper, Function<T, Stream<V>> valueMapper) { this.keyMapper = keyMapper; this.valueMapper = valueMapper; } public Function<T, Stream<FlatEntry<K, V>>> build() { return flatEntryMapper(keyMapper, valueMapper); } public <K2, V2> FlatEntryMapperBuilder<T, K, FlatEntry<K2, V2>> chain(Function<V, K2> keyMapper2, Function<V, Stream<V2>> valueMapper2) { return new FlatEntryMapperBuilder<>(keyMapper, valueMapper.andThen(stream -> stream.flatMap(flatEntryMapper(keyMapper2, valueMapper2)))); } } public static class FlatEntry<K, V> { public final K key; public final V value; public FlatEntry (K key, V value) { this.key = key; this.value = value; } } }
The problem is with its use. Let's say I have:
Map<String, Set<String>> level1Map;
I can display each element in subsets in FlatEntry by doing:
level1Map.entrySet().stream().flatMap(GdkStreams.flatEntryMapper(Entry::getKey, entry -> entry.getValue().stream()));
And everything works fine. But when I try to do this:
level1Map.entrySet() .stream() .flatMap(GdkStreams.flatEntryMapperBuilder(Entry::getKey, entry -> entry.getValue().stream()).build());
The eclipse compiler (Mars 4.5.0) is broken into:
- The type Map.Entry does not define getKey(Object) that is applicable here - The method getValue() is undefined for the type Object - Type mismatch: cannot convert from GdkStreams.FlatEntryMapperBuilder<Object,Object,Object> to <unknown>
And javac (1.8.0_51) is broken into:
MainTest.java:50: error: incompatible types: cannot infer type-variable(s) T,K#1,V#1 .flatMap(GdkStreams.flatEntryMapperBuilder(Entry::getKey, entry -> entry.getValue().stream()).build()); ^ (argument mismatch; invalid method reference method getKey in interface Entry<K#2,V#2> cannot be applied to given types required: no arguments found: Object reason: actual and formal argument lists differ in length) where T,K#1,V#1,K#2,V#2 are type-variables: T extends Object declared in method <T,K#1,V#1>flatEntryMapperBuilder(Function<T,K#1>,Function<T,Stream<V#1>>) K#1 extends Object declared in method <T,K#1,V#1>flatEntryMapperBuilder(Function<T,K#1>,Function<T,Stream<V#1>>) V#1 extends Object declared in method <T,K#1,V#1>flatEntryMapperBuilder(Function<T,K#1>,Function<T,Stream<V#1>>) K#2 extends Object declared in interface Entry V#2 extends Object declared in interface Entry MainTest.java:50: error: invalid method reference .flatMap(GdkStreams.flatEntryMapperBuilder(Entry::getKey, entry -> entry.getValue().stream()).build()); ^ non-static method getKey() cannot be referenced from a static context where K is a type-variable: K extends Object declared in interface Entry 2 errors
If I replace Entry::getKey with entry -> entry.getKey() , javac will dramatically change its result:
MainTest.java:51: error: cannot find symbol .flatMap(GdkStreams.flatEntryMapperBuilder(entry -> entry.getKey(), entry -> entry.getValue().stream()).build()); ^ symbol: method getKey() location: variable entry of type Object MainTest.java:51: error: cannot find symbol .flatMap(GdkStreams.flatEntryMapperBuilder(entry -> entry.getKey(), entry -> entry.getValue().stream()).build()); ^ symbol: method getValue() location: variable entry of type Object 2 errors
It compiles fine, specifying type parameters as I expected:
level1Map.entrySet() .stream() .flatMap(GdkStreams.<Entry<String, Set<String>>, String, String> flatEntryMapperBuilder(Entry::getKey, entry -> entry.getValue() .stream()) .build());
or by specifying one of the arguments type parameters:
Function<Entry<String, Set<String>>, String> keyGetter = Entry::getKey; level1Map.entrySet() .stream() .flatMap(GdkStreams.flatEntryMapperBuilder(keyGetter, entry -> entry.getValue().stream()).build());
But this is awkward! Imagine how awkward it would be to write all type parameters with 2 levels on the map using the chain method (which is my target use):
Map<String, Map<String, Set<String>>> level2Map;
I read a lot of other questions about pins like lambdas and generics, but no one answers my specific case.
Am I missing something? Can I fix my API so that its use is less awkward, or am I always stuck with type arguments? Thanks!