2015-10-08 13 views
8

Ho creato un metodo con argomenti tipo, restituendo un tipo generico utilizzando questi argomenti di tipo e prendendo argomenti Function che dipende anche dagli argomenti tipo. Quando uso lambdas come argomenti, il compilatore mi obbliga a specificare gli argomenti di tipo del metodo, il che sembra sbagliato.Impossibile utilizzare il metodo Java 8 con argomenti lambda senza specificare argomenti tipo

Sto progettando una classe di utilità con i metodi da utilizzare con Stream.flatMap. Gestisce ogni tipo di voce raccolta in un oggetto FlatEntry che contiene un elemento chiave e valore e può farlo su più livelli con un costruttore. Il metodo interessato è flatEntryMapperBuilder. Ecco il codice:

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

Il problema viene fornito con il suo utilizzo. Dire che ho:

Map<String, Set<String>> level1Map; 

Posso tracciare ogni elemento sub set ad un FlatEntry facendo:

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

e funziona bene. Ma quando provo a fare questo:

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

L'eclisse (Marte 4.5.0) Soggiorni compilatore con:

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

E javac (1.8.0_51) rompe con:

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 

Se sostituisco Entry::getKey per entry -> entry.getKey(), javac cambia drasticamente l'output:

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 

Si compila bene specificando parametri di tipo, che è quello che mi aspettavo:

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

o specificando uno degli argomenti digitare parametri:

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

Ma questo è goffo! Immaginate ora come goffa sarebbe di scrivere tutti i parametri di tipo con 2 livelli nella mappa, utilizzando il metodo a catena (che è il mio utilizzo di destinazione):

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

Ho letto molte altre domande su lambda e generici inferenza ma nessuno risponde al mio caso particolare.

mi sto perdendo qualcosa? Posso correggere la mia API in modo che il suo utilizzo sia meno impacciato o sono sempre bloccato a specificare gli argomenti di tipo? Grazie!

+0

Nel codice attuale si hanno due più argomenti tipo k2, v2, dovrebbe essere K e V? –

+0

Il metodo 'chain' ha più argomenti tipo, ma per il caso d'uso presentato non è usato (l'ho incluso per completezza). Il problema si verifica solo usando 'flatEntryMapperBuilder'. Rimuovere 'chain' dal tipo' FlatEntryMapperBuilder' non cambia il problema. –

+3

Questa è una limitazione nota dell'inferenza di tipo di Java 8: non funziona con invocazioni di metodi concatenati come 'genericFactoryMethod(). Build()'. – Holger

risposta

8

Holger ha avuto la migliore risposta nella sezione commenti, a mio parere:

Questa è una limitazione nota di Java 8 di inferenza di tipo: non funziona con invocazioni di metodi concatenati come genericFactoryMethod().build().

Grazie!A proposito il mio API, io specificare le funzioni prima di utilizzarli come argomenti, come questo:

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

EDIT: Ho ridisegnato l'API grazie ai commenti di Holger (grazie ancora!). Mantiene l'elemento originale invece di una chiave, insieme al valore appiattito.

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

È chainable, partendo con il livello 2 il mappatore deve elaborare un FlatEntry. L'utilizzo è simile a un semplice 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())); 
+0

Direi che si tratta di un problema con l'inferenza nel compilatore Java 8 ** **. Prendiamo come esempio: 'final Set expectedDaoData3 = Collections.unmodifiableSet ( Arrays.asList (" a "," b "," c "). Stream(). Collect (Collectors.toCollection (LinkedHashSet :: nuovo))); ' compila con Eclipse Compiler per Java, ma non OpenJDK, che si lamenta con: ' richiesto: java.util.Set trovato: java.util.Collection motivo: impossibile dedurre le variabili di tipo E' –

3

Un modo per fornire informazioni sul tipo abbastanza per il compilatore è dichiarare un tipo esplicito di uno dell'argomento lambda. Questo è nello stesso spirito di your answer ma un po 'più compatto, dal momento che devi solo fornire il tipo di argomento, non l'intera funzione.

Questo sembra piuttosto bene per la mappa di un livello:

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

La mappa a due livelli si trova al confine al grottesco, però:

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

Non sapevo nemmeno che fosse possibile! Grazie! –

Problemi correlati