不能使用带有lambda参数的Java 8方法,而不指定类型参数 [英] Cannot use Java 8 method with lambda arguments without specifying type arguments

查看:4261
本文介绍了不能使用带有lambda参数的Java 8方法,而不指定类型参数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



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

  import java.util.function.Function; 
import java.util.stream.Stream;

public class GdkStreams
{
public static< T,K,V>函数< T,Stream< FlatEntry< K,V>>> flatEntryMapper(Function< T,K> keyMapper,
Function< T,Stream< V>> valueMapper)
{
return input - > {
K key = keyMapper.apply(input);
返回valueMapper.apply(input).map(value - > new FlatEntry<>(key,value));
};
}

public static< T,K,V> FlatEntryMapperBuilder< T,K,V> flatEntryMapperBuilder(函数< T,K> keyMapper,
函数< 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>> (函数< V,K2> keyMapper2,
函数< V,Stream< V2>> valueMapper2)
{
返回新的FlatEntryMapperBuilder<>(keyMapper,
valueMapper。和stream(> stream.flatMap(flatEntryMapper(keyMapper2,
valueMapper2))));
}
}

public static class FlatEntry< K,V>
{
public final K key;

公共最终V值;

public FlatEntry(K key,V value)
{
this.key = key;
this.value = value;
}
}
}

。假设我有:

 地图< String,Set< String>> 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)编译器打破了:

   - 类型Map.Entry没有定义在这里适用的getKey(Object)
- 方法getValue()未定义为类型Object
- 类型不匹配:无法从GdkStreams.FlatEntryMapperBuilder转换< Object,Object,Object>至
<未知>

而javac(1.8.0_51)则会打破:

  MainTest.java:50:错误:不兼容的类型:无法推断类型变量T,K#1,V#1 
.flatMap(GdkStreams。 flatEntryMapperBuilder(Entry :: getKey,entry - > entry.getValue()。stream())。build());
^
(参数不匹配;无效方法参考
方法getKey在接口Entry< K#2,V#2>中不能应用于给定类型
required:无参数
found:Object
reason:实际和形式参数列表的长度不同)
其中T,K#1,V#1,K#2,V#2是类型变量:
T扩展在方法< T,K#1,V#1> flatEntryMapperBuilder(Function< T,K#1> ;, Function< T,Stream< V#1>)>中所声明的Object
K#1在< T,K#1,V#1> flatEntryMapperBuilder(Function< T,K#1>,Function< T,Stream< V#1>>)
V#1 extends Object在< T,K#1,V#1> flatEntryMapperBuilder(Function< T,K#1>,Function< T,Stream< V#1>>)方法中声明的
K#接口入口
V#2扩展在接口入口
中声明的对象MainTest.java:50:错误:无效的方法参考
.flatMap(GdkStreams.flatEntryMapperBuilder(Entry :: getKey,entry - > entry.getValue()流())建立())。。
^
非静态方法getKey()不能从静态上下文中引用
其中K是类型变量:
K扩展在接口Entry中声明的对象
2错误

如果我替换 Entry :: getKey 入口 - >

  MainTest.java:51:error.input.getKey() :无法找到符号
.flatMap(GdkStreams.flatEntryMapperBuilder(entry - > entry.getKey(),entry - > entry.getValue()。stream())。build());

$
符号:方法getKey()
位置:Object类型的变量条目
MainTest.java:51:错误:找不到符号
。 flatMap(GdkStreams.flatEntryMapperBuilder(entry - > entry.getKey(),entry - > entry.getValue()。stream())。build());


符号:方法getValue()
位置:类型为Object的变量条目
2错误

它通过指定类型参数进行编译,这正是我所期望的:

 <$ c < Entry< String,Set< String>>,String,String> flatEntryMapperBuilder(Entry :: getKey,$($))
.stream()
.flatMap(GdkStreams。 b $ b入口 - > entry.getValue()
.stream())
.build());

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

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

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

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

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

我错过了什么吗?我可以纠正我的API,使其使用不笨拙,或者我总是指定类型参数?谢谢!

解决方案

Holger在评论部分有最佳答案,我认为:


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


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

 函数< Entry< String,Set< String>>,String> keyMapper = Entry :: getKey; 
函数< Entry< String,Set< String>> ;, Stream< String>> valueMapper = entry - > entry.getValue()流()。

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

  public static< T,R>功能< ;? 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>
{
/ **原始流元素* /
public final E元素;

/ **展平值* /
公共最终V值;

private FlatEntry(E元素,V值)
{
this.element = element;
this.value = value;




$ b $ p
$ b

它是可链接的,从级别2开始,处理 FlatEntry 。用法类似于简单的 flatMap

  Map< String, Map< String,Map< String,Set< String>>>> level3Map; 

//给出所有展平值的流
level3Map.entrySet()
.stream()
.flatMap(entry - > entry.getValue ).entrySet()。stream())
.flatMap(entry - > entry.getValue()。entrySet()。stream())
.flatMap(entry - > entry.getValue )。流());

//给出了一个FlatEntries流,其中包含所有FlatEntries元素,并且它们的所有原始元素嵌套在FlatEntries中
level3Map.entrySet()
.stream()
.flatMap( GdkStreams.flatEntryMapper(entry - > entry.getValue().inputSet().stream()))
.flatMap(GdkStreams.flatEntryMapper(flatEntry - > flatEntry.value.getValue().entrySet())。 stream()))
.flatMap(GdkStreams.flatEntryMapper(flatEntry - > flatEntry.value.getValue()。stream()));


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.

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:

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:

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:

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

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:

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):

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.

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!

解决方案

Holger had the best answer in the comment section in my opinion:

This is a known limitation of Java 8’s type inference: it doesn’t work with chained method invocations like genericFactoryMethod().build().

Thanks! About my API, I will specify the functions before using them as arguments, like this:

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.

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:

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()));

这篇关于不能使用带有lambda参数的Java 8方法,而不指定类型参数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆