Ne peut pas utiliser Java 8 méthode lambda arguments sans spécifier des arguments de type

J'ai fait une méthode avec des arguments de type, de retour d'un type générique à l'aide de ces arguments de type, et en prenant Function arguments qui dépend également du type d'arguments. Lorsque j'utilise lambdas comme arguments, le compilateur m'oblige à spécifier le type des arguments de la méthode, qui se sent mal.

Je suis en train de concevoir un utilitaire de classe avec des méthodes à utiliser avec Stream.flatMap. Il fait correspondre à chaque type de collection à l'entrée de FlatEntry qui contient une clé et de la valeur de l'élément, et peut faire sur plusieurs niveaux avec un constructeur. Le touché de la méthode est flatEntryMapperBuilder. Voici le 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;
}
}
}

Le problème vient de son utilisation. Dire que j'ai:

Map<String, Set<String>> level1Map;

Je peux la carte de chaque élément dans le sous-Ensembles d'un FlatEntry par:

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

Et il fonctionne très bien. Mais quand j'essaie de le faire:

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

L'éclipse (Mars 4.5.0) compilateur rompt avec:

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

Et javac (1.8.0_51) rompt avec:

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

Si je remplace Entry::getKey par entry -> entry.getKey(), javac change son drastiquement la production:

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

Il compile fine en spécifiant les paramètres de type, qui est ce que j'attendais:

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

ou en spécifiant l'un des arguments les paramètres de type:

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

Mais c'est maladroit! Imaginez maintenant comment maladroit, il serait d'écrire tous les paramètres de type 2 niveaux dans la carte, à l'aide de la chaîne de la méthode (ce qui est mon objectif de l'utilisation):

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

J'ai lu beaucoup d'autres questions sur les lambdas et des génériques de l'inférence de type, mais aucun n'est de répondre à mon cas particulier.

Suis-je raté quelque chose? Puis-je corriger mon API pour que son utilisation est moins maladroit, ou suis-je coincé avec toujours en spécifiant le type d'arguments? Merci!

Dans votre code, vous avez deux arguments de type k2,v2 , faut-il K et V?
C'est une limitation connue de Java 8 est l'inférence de type: il ne fonctionne pas avec enchaîné les invocations de méthode comme genericFactoryMethod().build().
Mais vous n'avez pas besoin que le constructeur vous pouvez la chaîne de la fonction: .stream().flatMap(flatEntryMapper(Entry::getKey, entry -> entry.getValue().stream().flatMap(flatEntryMapper(…))))...
Vous ne devriez pas avoir besoin de la fonction de composition. Un .stream().flatMap(flatEntryMapper(functionFoo1, functionBar1)) .flatMap(flatEntryMapper(functionFoo2, functionBar2)) .flatMap(flatEntryMapper(functionFoo3, functionBar3)) devrait faire aussi bien...
Vraiment? Je pensais que c'est ce que flatEntryMapper(…). Notez que dans mon exemple de code, il est encore utilisé.

OriginalL'auteur David Dersigny | 2015-10-08