2016-02-22 11 views
13

Durante la manipolazione di stream Java 8 ho riscontrato un errore in cui il compilatore sembra "dimentica" il tipo i miei parametri generici.Java Stream Generics Tipo non corrispondente

Il seguente frammento di codice crea un flusso di nomi di classi e tenta di associare il flusso a un flusso di Class<? extends CharSequence>.

public static Stream<Class<? extends CharSequence>> getClasses() { 

    return Arrays.asList("java.lang.String", "java.lang.StringBuilder", "Kaboom!") 
     .stream() 
     .map(x -> { 
      try { 
       Class<?> result = Class.forName(x); 

       return result == null ? null : result.asSubclass(CharSequence.class); 
      } catch (Exception e) { 
       // TODO Auto-generated catch block 
       e.printStackTrace(); 
      } 

      return null; 
     }) 
     //.filter(x -> x != null) 
     ; 

} 

Quando ho rimuovere il commento il filtro per rimuovere le voci nulli dal flusso ottengo un errore di compilazione

Tipo non corrispondente: non può convertire da Class < cattura # 15-di? estende CharSequence > alla Classe <oggetto>

Qualcuno può spiegare a me perché aggiungendo il filtro fa sì che questo errore?

PS: Il codice qui è alquanto arbitrario ed è abbastanza semplice per far sparire l'errore: Assegnare il flusso mappato a una variabile temporanea prima di applicare il filtro. Quello che mi interessa è perché lo snippet di codice sopra lo genera un errore di compilazione.

Modifica: come sottolineato da @Holger, questa domanda non è un duplicato esatto di Java 8 Streams: why does Collectors.toMap behave differently for generics with wildcards? perché lo snippet problematico al momento viene compilato senza problemi mentre lo snippet qui non lo è.

+1

Huh. All'inizio pensavo che fosse dovuto a [metodo di concatenazione] (http://stackoverflow.com/q/24794924/1743880) ma non ne sono sicuro. 'asSubclass' restituisce veramente una' Classe '. Non si compila anche con 'javac', almeno 1.8.0_74. – Tunaki

+1

Posso farlo compilare con la chiamata a 'filter' con un argomento di tipo esplicito a' map': '. > map ('. Oppure posso farlo compilare con' return CharSequence.class' invece di 'return null' dopo il blocco' catch'. Sembra un problema con l'inferenza di tipo – rgettman

+0

Non è un problema con javac 1.8.0_31. – saka1029

risposta

1

Questo è causa di inferenza di tipo:

Il tipo "indovinato" dal suo obiettivo: sappiamo che mappa (qualsiasi cosa) deve restituire un "Stream<Class<? extends CharSequence>>" perché è il tipo di ritorno della funzione. Ad esempio, se eseguiamo il ritorno a un'altra operazione, un filtro o una mappa, perdiamo questo tipo di inferenza (non può passare attraverso le concatenazioni)

L'inferenza del tipo ha i suoi limiti e la trovi.

La soluzione è semplice: hai detto che se si utilizza una variabile, è possibile specificare il target, quindi aiutare l'inferenza del tipo.

Questa compilazione:

public static Stream<Class<? extends CharSequence>> getClasses() { 
Stream<Class<? extends CharSequence>> map1 = Arrays.asList ("java.lang.String", "java.lang.StringBuilder", "Kaboom!").stream().map (x -> { 
    try { 
    Class<?> result = Class.forName (x); 
    return result == null ? null : result.asSubclass(CharSequence.class); 
    } catch (Exception e) { 
    // TODO Auto-generated catch block 
    e.printStackTrace(); 
    } 

    return null; 
}); 
return map1.filter(x -> x != null); 

noti che ho modificato il codice per tornare sempre nullo per dimostrare che tipo infered non proviene dal tipo di ritorno lambda.

E vediamo che il tipo di map1 è dedotto dalla dichiarazione di variabile, il suo obiettivo.Se torniamo esso, è equivalente, il target è il tipo di ritorno, ma se ci si concatenare:

Questo non compilare:

public static Stream<Class<? extends CharSequence>> getClasses() { 

return Arrays.asList ("java.lang.String", "java.lang.StringBuilder", "Kaboom!").stream().map (x -> { 
    try { 
    Class<?> result = Class.forName (x); 
    return result == null ? null : result.asSubclass(CharSequence.class); 
    } catch (Exception e) { 

    e.printStackTrace(); 
    } 

    return null; 
}).filter(x -> x != null); 

La prima dichiarazione mappa ha alcun obiettivo, in modo che l'infered tipo è definito per impostazione predefinita: Stream<Object>

Modifica

Un altro modo per farlo funzionare sarebbe quello di fare il tipo di lavoro di inferenza con valore di ritorno Lambda (invece del target), è necessario specificare ° per esempio, tipo di ritorno con cast. Questo compilerà:

public static Stream<Class<? extends CharSequence>> getClasses2() { 

return Arrays.asList ("java.lang.String", "java.lang.StringBuilder", "Kaboom!").stream().map (x -> { 
    try { 
    Class<?> result = Class.forName (x); 
    return (Class<? extends CharSequence>)(result == null ? null : result.asSubclass(CharSequence.class)); 
    } catch (Exception e) { 
    // TODO Auto-generated catch block 
    e.printStackTrace(); 
    } 

    return (Class<? extends CharSequence>)null; 
}).filter(x -> x != null); 

}

Si noti che questo è a causa del funzionamento concatenamento, è possibile sostituire .filter (! X -> x = null) con la mappa (x-> x) si avrebbe lo stesso problema.

Modifica: modifica gli esempi per soddisfare esattamente la domanda.

+0

Alla tua risposta manca completamente la domanda. La domanda riguarda un problema causato dall'operazione 'filter (...)' che non usi in nessuno dei tuoi esempi di codice. Il fatto che 'null' possa essere assegnato ad ogni tipo o che la compilazione fallisca quando si sostituisce la' Classe 'con una' Classe non corrispondente ' 'non è utile in alcun modo. Mantenere il codice originale del questionario è più semplice delle "soluzioni" in quanto l'interrogante già crea uno stream del tipo * correct * che funziona già quando non si concatena un'operazione 'filter (...)'. – Holger

+0

Il problema non è causato dal filtro in particolare, ma da qualsiasi operazione, potrebbe essere map(), filter() o qualunque(). e ho lasciato il filtro nel mio codice, può essere decomposto per ottenere il codice originale. – pdem

+0

Il codice OPs viene compilato senza errori senza l'operazione 'filter' e tutto ciò che fai è solo complicare il codice OPs senza risolvere nulla. Quando aggiungi l'operazione 'filter', compare lo stesso errore. – Holger

0

Oltre a @pdem's answer, anche questo funziona per voi:

public class Test { 

    public static void main(String[] args) { 
     getAsSubclasses(CharSequence.class, "java.lang.String", "java.lang.StringBuilder", "Kaboom!") 
       .forEach(System.out::println); 
    } 

    public static <C> Stream<Class<? extends C>> getAsSubclasses(Class<C> type, String... classNames) { 
     return Arrays.stream(classNames) 
       .map(new ToSubclass<>(type)) 
       .filter(c -> c != null); 
    } 

    static final class ToSubclass<C> implements Function<String, Class<? extends C>> { 

     final Class<C> type; 

     ToSubclass(Class<C> type) { 
      this.type = type; 
     } 

     @Override 
     public Class<? extends C> apply(String s) { 
      try { 
       return Class.forName(s).asSubclass(type); 
      } catch (Exception e) { 
       return null; 
      } 
     } 

    } 

} 
0

Perché tipo di ritorno della funzione lambda non può essere determinato (o compilatore semplicemente non cerca di farlo) in modo corretto. Utilizzando esplicito anonimo Function oggetto con parametri di tipo corretti rimuove completamente problemi con inferenza di tipo:

public static Stream<Class<? extends CharSequence>> getClasses() { 

    return Arrays.asList("java.lang.String", 
         "java.lang.StringBuilder", 
         "Kaboom!") 
    .stream().map(
     new Function<String, Class<? extends CharSequence>>() { 
      public Class<? extends CharSequence> apply(String name) { 
       try { 
        return Class.forName(name).asSubclass(CharSequence.class); 
       } catch (Exception e) { 
       } 
       return null; 
      } 
     } 
    ).filter(Objects::nonNull); 

} 

vedere e cosa reale tipo di ritorno della funzione lambda si risolve con compilatore, provate a chiedere Eclipse per assegnare l'espressione ...stream().map(<your initial lambda>) alla variabile locale (premere Ctrl+2, quindi L con il cursore in piedi subito prima dell'espressione). È il tipo di ritorno Stream<Class<? extends Object>> risolto dal compilatore, non previsto Stream<Class<? extends CharSequence>>.

+0

Eh, quando cambi il tipo di ritorno in 'Stream ', non devi modificare nulla nel codice originale, questa è una soluzione economica, se vogliamo chiamarla una soluzione ... – Holger

+0

Giusto sei, significava il desiderato digita, è appena mancato quando viene modificato il codice qui –

+1

Questo risolve il problema, ma preferirei usare un tipo esplicito insieme a un'espressione lambda, ad es. 'Flusso(). > map (name -> {try {return Class.forome (nome) .asSubclass (CharSequence.class);} catch (Exception e) {} return null;}). filter (...) 'ma nota che un spiegazione su * perché * non è sufficiente quando include il comportamento di Eclipse. Eclipse è ... molto speciale, quando si tratta di inferenza di tipo. Per 'javac', l'aggiunta di un' .map (x-> x) 'alla fine risolve anche il problema, mostrando chiaramente che non ha mai dedotto'? estende l'oggetto qui. È più complicato – Holger