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!
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
Vous devez vous connecter pour publier un commentaire.
Holger a la meilleure réponse dans la section des commentaires, à mon avis:
Merci! Sur mon API, je vais spécifier les fonctions avant de les utiliser comme arguments, comme ceci:
EDIT: j'ai redessiné l'API grâce à Holger commentaires (merci encore!). Il garde à l'élément d'origine au lieu d'une clé, ainsi que la valeur aplatie.
Il est chainable, en commençant par le niveau 2 le mappeur a à traiter un
FlatEntry
. L'utilisation est similaire à un simpleflatMap
:final Set<String> expectedDaoData3 = Collections.unmodifiableSet( Arrays.asList("a", "b", "c").stream().collect(Collectors.toCollection(LinkedHashSet::new)));
Il compile avec le Compilateur Eclipse pour Java, mais pas OpenJDK, qui se plaint avec:required: java.util.Set<? extends T> found: java.util.Collection<java.lang.String> reason: cannot infer type-variable(s) E
OriginalL'auteur David Dersigny
Une façon de fournir suffisamment d'informations de type pour le compilateur est de déclarer un type explicite de l'un des lambda argument. C'est dans le même esprit que votre réponse mais un peu plus compact, puisque vous n'avez qu'à fournir le type de l'argument, et non l'ensemble de la fonction.
Cela semble assez correct pour le niveau de la carte:
Les deux au niveau de la carte est sur la frontière de la grotesque, cependant:
OriginalL'auteur Lii