不能在不指定类型参数的情况下使用带有 lambda 参数的 Java 8 方法
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/33016329/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Cannot use Java 8 method with lambda arguments without specifying type arguments
提问by David Dersigny
I made a method with type arguments, returning a generic type using these type arguments, and taking Function
arguments which also depends on the type arguments. When I use lambdas as arguments, the compiler forces me to specify the type arguments of the method, which feels wrong.
我创建了一个带有类型参数的方法,使用这些类型参数返回一个泛型类型,并采用Function
也取决于类型参数的参数。当我使用 lambdas 作为参数时,编译器强制我指定方法的类型参数,这感觉不对。
I am designing a utility class with methods to use with Stream.flatMap
. It maps every kind of collection entry to a FlatEntry which contains a key and value element, and can do this on multiple levels with a builder. The affected method is flatEntryMapperBuilder
. Here is the code:
我正在设计一个实用程序类,其中包含与Stream.flatMap
. 它将各种集合条目映射到包含键和值元素的 FlatEntry,并且可以使用构建器在多个级别上执行此操作。受影响的方法是flatEntryMapperBuilder
。这是代码:
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 comes with its usage. Say I have:
问题在于它的使用。说我有:
Map<String, Set<String>> level1Map;
I can map every element in the sub Sets to a FlatEntry by doing:
我可以通过执行以下操作将子集中的每个元素映射到 FlatEntry:
level1Map.entrySet().stream().flatMap(GdkStreams.flatEntryMapper(Entry::getKey, entry -> entry.getValue().stream()));
And it works just fine. But when I try to do this:
它工作得很好。但是当我尝试这样做时:
level1Map.entrySet()
.stream()
.flatMap(GdkStreams.flatEntryMapperBuilder(Entry::getKey, entry -> entry.getValue().stream()).build());
The eclipse (Mars 4.5.0) compiler breaks with:
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<Object,Object,Object> to
<unknown>
And javac (1.8.0_51) breaks with:
并且 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<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
by entry -> entry.getKey()
, javac changes its output drastically:
如果我替换Entry::getKey
为entry -> 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
It compiles fine by specifying type parameters, which is what I expected:
它通过指定类型参数编译得很好,这正是我所期望的:
level1Map.entrySet()
.stream()
.flatMap(GdkStreams.<Entry<String, Set<String>>, String, String> flatEntryMapperBuilder(Entry::getKey,
entry -> entry.getValue()
.stream())
.build());
or 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 clumsy! Imagine now how clumsy it would be to write all type parameters with 2 levels in the map, using the chain method (which is my target usage):
但这很笨拙!现在想象一下,使用链式方法(这是我的目标用法)在映射中编写所有具有 2 个级别的类型参数是多么笨拙:
Map<String, Map<String, Set<String>>> level2Map;
I have read many other questions about lambdas and generics type inference but none is answering my particular case.
我已经阅读了许多关于 lambdas 和泛型类型推断的其他问题,但没有人回答我的特殊情况。
Am I missing something? Can I correct my API so that its usage is less clumsy, or am I stuck with always specifying type arguments? Thanks!
我错过了什么吗?我可以更正我的 API 以使其使用不那么笨拙,还是我总是坚持指定类型参数?谢谢!
采纳答案by David Dersigny
Holger had the best answer in the comment section in my opinion:
在我看来,Holger 在评论部分给出了最好的答案:
This is a known limitation of Java 8's type inference: it doesn't work with chained method invocations like
genericFactoryMethod().build()
.
这是 Java 8 类型推断的一个已知限制:它不适用于像
genericFactoryMethod().build()
.
Thanks! About my API, I will specify the functions before using them as arguments, like this:
谢谢!关于我的 API,我将在将它们用作参数之前指定这些函数,如下所示:
Function<Entry<String, Set<String>>, String> keyMapper = Entry::getKey;
Function<Entry<String, Set<String>>, Stream<String>> valueMapper = entry -> entry.getValue().stream();
EDIT: I redesigned the API thanks to Holger's comments (thanks again!). It keeps the original element instead of a key, along with the flattened value.
编辑:感谢 Holger 的评论(再次感谢!),我重新设计了 API。它保留原始元素而不是键,以及扁平化的值。
public static <T, R> Function<? super T, Stream<FlatEntry<T, R>>> flatEntryMapper(Function<? super T, ? extends Stream<? extends R>> mapper)
{
return element -> mapper.apply(element).map(value -> new FlatEntry<>(element, value));
}
public static class FlatEntry<E, V>
{
/** 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;
}
}
It is chainable, starting with level 2 the mapper has to process a FlatEntry
. The usage is similar to a simple flatMap
:
它是可链接的,从级别 2 开始,映射器必须处理FlatEntry
. 用法类似于一个简单的flatMap
:
Map<String, Map<String, Map<String, Set<String>>>> 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()));
回答by Lii
One way to provide enough type information to the compiler is to declare an explicit type of one of the lambda argument. This is in the same spirit as your answerbut a little more compact, since you only have to provide the type of the argument, not the whole function.
向编译器提供足够类型信息的一种方法是声明 lambda 参数之一的显式类型。这与您的答案具有相同的精神,但更加紧凑,因为您只需提供参数的类型,而不是整个函数。
This looks pretty okay for the one-level map:
对于单级地图,这看起来很不错:
level1Map.entrySet().stream()
.flatMap(GdkStreams.flatEntryMapperBuilder(
(Entry<String, Set<String>> entry) -> entry.getKey(),
entry -> entry.getValue().stream()).build());
The two-level map is on the border to the grotesque, however:
两级地图在怪诞的边界上,但是:
level2Map.entrySet().stream()
.flatMap(GdkStreams.flatEntryMapperBuilder(
(Entry<String, Map<String, Set<String>>> entry1) -> entry1.getKey(),
entry1 -> entry1.getValue().entrySet().stream()
.flatMap(GdkStreams.flatEntryMapperBuilder(
(Entry<String, Set<String>> entry2) -> entry2.getKey(),
entry2 -> entry2.getValue().stream()).build())).build());