不指定类型参数,不能将Java 8方法与lambda参数一起使用

我使用类型参数创建了一个方法,使用这些类型参数返回generics类型,并使用Function参数,这也取决于类型参数。 当我使用lambdas作为参数时,编译器强制我指定方法的类型参数,这感觉不对。

我正在设计一个实用程序类,其中包含与Stream.flatMap一起使用的方法。 它将每种集合条目映射到包含键和值元素的FlatEntry,并且可以使用构建器在多个级别上执行此操作。 受影响的方法是flatEntryMapperBuilder 。 这是代码:

 import java.util.function.Function; import java.util.stream.Stream; public class GdkStreams { public static  Function<T, Stream<FlatEntry>> flatEntryMapper(Function keyMapper, Function<T, Stream> valueMapper) { return input -> { K key = keyMapper.apply(input); return valueMapper.apply(input).map(value -> new FlatEntry(key, value)); }; } public static  FlatEntryMapperBuilder flatEntryMapperBuilder(Function keyMapper, Function<T, Stream> valueMapper) { return new FlatEntryMapperBuilder(keyMapper, valueMapper); } public static class FlatEntryMapperBuilder { private Function keyMapper; private Function<T, Stream> valueMapper; private FlatEntryMapperBuilder (Function keyMapper, Function<T, Stream> valueMapper) { this.keyMapper = keyMapper; this.valueMapper = valueMapper; } public Function<T, Stream<FlatEntry>> build() { return flatEntryMapper(keyMapper, valueMapper); } public  FlatEntryMapperBuilder<T, K, FlatEntry> chain(Function keyMapper2, Function<V, Stream> valueMapper2) { return new FlatEntryMapperBuilder(keyMapper, valueMapper.andThen(stream -> stream.flatMap(flatEntryMapper(keyMapper2, valueMapper2)))); } } public static class FlatEntry { public final K key; public final V value; public FlatEntry (K key, V value) { this.key = key; this.value = value; } } } 

问题来自于它的使用。 说我有:

 Map<String, Set> level1Map; 

我可以通过执行以下操作将子集中的每个元素映射到FlatEntry:

 level1Map.entrySet().stream().flatMap(GdkStreams.flatEntryMapper(Entry::getKey, entry -> entry.getValue().stream())); 

它工作得很好。 但是当我尝试这样做时:

 level1Map.entrySet() .stream() .flatMap(GdkStreams.flatEntryMapperBuilder(Entry::getKey, entry -> entry.getValue().stream()).build()); 

eclipse(Mars 4.5.0)编译器打破了:

 - 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 to  

而javac(1.8.0_51)打破:

 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 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 flatEntryMapperBuilder(Function,Function<T,Stream>) K#1 extends Object declared in method flatEntryMapperBuilder(Function,Function<T,Stream>) V#1 extends Object declared in method flatEntryMapperBuilder(Function,Function<T,Stream>) 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 

如果我用entry -> entry.getKey()替换Entry::getKey ,javac会大大改变它的输出:

 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 

它通过指定类型参数编译得很好,这是我所期望的:

 level1Map.entrySet() .stream() .flatMap(GdkStreams.<Entry<String, Set>, String, String> flatEntryMapperBuilder(Entry::getKey, entry -> entry.getValue() .stream()) .build()); 

或指定其中一个参数类型参数:

 Function<Entry<String, Set>, String> keyGetter = Entry::getKey; level1Map.entrySet() .stream() .flatMap(GdkStreams.flatEntryMapperBuilder(keyGetter, entry -> entry.getValue().stream()).build()); 

但这很笨拙! 现在想象一下,使用链式方法(这是我的目标用法)在地图中用2个级别编写所有类型参数是多么笨拙:

 Map<String, Map<String, Set>> level2Map; 

我已经阅读了许多关于lambdas和generics类型推断的其他问题,但没有人回答我的具体情况。

我错过了什么吗? 我可以更正我的API,以便它的用法不那么笨拙,还是我总是指定类型参数? 谢谢!

在我看来,Holger在评论部分得到了最好的答案:

这是Java 8类型推断的一个已知限制:它不适用于像genericFactoryMethod().build()这样的链式方法调用。

谢谢! 关于我的API,我将在将它们用作参数之前指定函数,如下所示:

 Function>, String> keyMapper = Entry::getKey; Function>, Stream> valueMapper = entry -> entry.getValue().stream(); 

编辑:感谢Holger的评论,我重新设计了API(再次感谢!)。 它保留原始元素而不是键,以及展平值。

 public static  Function>> flatEntryMapper(Function> mapper) { return element -> mapper.apply(element).map(value -> new FlatEntry<>(element, value)); } public static class FlatEntry { /** The original stream element */ public final E element; /** The flattened value */ public final V value; private FlatEntry (E element, V value) { this.element = element; this.value = value; } } 

它是可链接的,从第2级开始,映射器必须处理FlatEntry 。 用法类似于简单的flatMap

 Map>>> level3Map; // gives a stream of all the flattened values level3Map.entrySet() .stream() .flatMap(entry -> entry.getValue().entrySet().stream()) .flatMap(entry -> entry.getValue().entrySet().stream()) .flatMap(entry -> entry.getValue().stream()); // gives a stream of FlatEntries with flattened values and all their original elements in nested FlatEntries level3Map.entrySet() .stream() .flatMap(GdkStreams.flatEntryMapper(entry -> entry.getValue().entrySet().stream())) .flatMap(GdkStreams.flatEntryMapper(flatEntry -> flatEntry.value.getValue().entrySet().stream())) .flatMap(GdkStreams.flatEntryMapper(flatEntry -> flatEntry.value.getValue().stream())); 

向编译器提供足够类型信息的一种方法是声明lambda参数之一的显式类型。 这与你的答案精神相同,但更紧凑,因为你只需要提供参数的类型,而不是整个函数。

对于单级地图,这看起来很不错:

 level1Map.entrySet().stream() .flatMap(GdkStreams.flatEntryMapperBuilder( (Entry> entry) -> entry.getKey(), entry -> entry.getValue().stream()).build()); 

两级地图位于怪诞的边界,但是:

 level2Map.entrySet().stream() .flatMap(GdkStreams.flatEntryMapperBuilder( (Entry>> entry1) -> entry1.getKey(), entry1 -> entry1.getValue().entrySet().stream() .flatMap(GdkStreams.flatEntryMapperBuilder( (Entry> entry2) -> entry2.getKey(), entry2 -> entry2.getValue().stream()).build())).build());