2016-02-10 17 views
14

Mentre studia sul Java8 Streams, mi sono imbattuto nel seguente frammento di codice:difficoltà a capire limiti inferiori quando viene utilizzato con lambda e interfaccia funzionale

Predicate<? super String> predicate = s -> s.startsWith("g"); 

Dal momento che il parametro generico è un limite inferiore, ho pensato che questo non sarebbe compilare. Il modo in cui lo vedo, se un oggetto è un supertipo di una stringa, quindi il passaggio in un tipo di oggetto dovrebbe interromperlo, poiché Object non ha una funzione startsWith(). Tuttavia, sono stato sorpreso di vederlo funzionare senza problemi.

Inoltre ancora, quando ho ottimizzato il predicato di prendere un limite superiore:

<? extends String>, 

non sarebbe la compilazione.

Pensavo di aver capito il significato di limiti superiori e inferiori, ma ovviamente, mi manca qualcosa. Qualcuno può aiutare a spiegare perché il limite inferiore funziona con questo lambda?

+2

Anche 'extends' compila -' Predicato predicate = s -> s.startsWith ("g") '- sebbene, non possiamo alimentare un' String' a questo predicato :) vedi anche il mio [articolo] (http://bayou.io/draft/ Capturing_Wildcards.html) su caratteri jolly – ZhongYu

+1

http://stackoverflow.com/questions/33085151/bad-return-type-in-lambda-expression/33085893#33085893 – ZhongYu

+1

'Predicato 'does * not * implica che è possibile passare' Object' ad esso (e provare così creerà un errore del compilatore). La firma 'Predicato 'implica semplicemente che * potrebbe * essere un' Predicato 'che è ok, in quanto quel predicato è ancora in grado di elaborare input' String'. In altre parole, 'Predicato 'dice che questo predicato può consumare stringhe, indipendentemente dal suo tipo effettivo, quindi' Predicato 'è un'implementazione valida di' Predicato '. – Holger

risposta

8

Il tipo di argomento lambda è esatto, non può essere ? super o ? extends. Questo è coperto da JLS 15.27.3. Type of a Lambda Expression. Introduce il concetto di target di terra tipo (che è fondamentalmente il tipo lambda). Tra le altre cose è affermato che:

Se T è un tipo interfaccia funzionale jolly-parametrizzato e l'espressione lambda è implicitamente tipizzato, quindi il tipo di destinazione terra è la parametrizzazione non jolly (§9.9) di T.

Enfasi mia. Quindi essenzialmente quando scrivi

Predicate<? super String> predicate = s -> s.startsWith("g"); 

Il tuo tipo di lambda è Predicate<String>. E 'lo stesso di:

Predicate<? super String> predicate = (Predicate<String>)(s -> s.startsWith("g")); 

O anche

Predicate<String> pred = (Predicate<String>)(s -> s.startsWith("g")); 
Predicate<? super String> predicate = pred; 

Dato il fatto che di tipo lambda argomenti sono di cemento, dopo che si applicano le normali regole di conversione di tipo: Predicate<String> è un Predicate<? super String> o Predicate<? extends String>. Quindi è necessario compilare sia Predicate<? super String> e Predicate<? extends String>. Ed entrambi funzionano per me su javac 8u25, 8u45, 8u71 e ecj 3.11.1.

+0

Grazie per il chiarimento. Il comportamento predefinito di Lambda con parametri limitati era al centro della mia confusione. – piper1970

7

L'ho appena testato, il compito stesso viene compilato. Ciò che cambia è se è effettivamente possibile chiamare predicate.test().

Facciamo un passo indietro e utilizzare un segnaposto GenericClass<T> per la spiegazione. Per gli argomenti tipo, Foo estende Bar e Bar estende Baz.

Estende: Quando si dichiara una GenericClass<? extends Bar>, che stai dicendo "Io non so che cosa il suo argomento di tipo generico in realtà è, ma è una sottoclasse di Bar." L'istanza effettiva avrà sempre un argomento di tipo non jolly, ma in questa parte del codice non si sa quale sia il suo valore. Considerare ora cosa significa per le invocazioni dei metodi.

Sai che cosa hai effettivamente ottenuto è uno GenericClass<Foo> o uno GenericClass<Bar>. Considera un metodo che restituisce T. Nel primo caso, il suo tipo di reso è Foo. Nel secondo, Bar. In entrambi i casi, si tratta di un sottotipo di Bar ed è sicuro da assegnare a una variabile Bar.

Considerare un metodo che ha un parametro T. Se si tratta di un GenericClass<Foo>, passarlo a Bar è un errore - Bar non è un sottotipo di Foo.

Quindi, con un limite superiore è possibile utilizzare valori di ritorno generici, ma non parametri di metodo generici.

Super: Quando si dichiara una GenericClass<? super Bar>, si sta dicendo "Io non so che cosa il suo argomento di tipo generico in realtà, ma è una superclasse di Bar." Considerare ora cosa significa per le invocazioni dei metodi.

Sai che cosa hai effettivamente ottenuto è uno GenericClass<Bar> o uno GenericClass<Baz>. Considera un metodo che restituisce T. Nel primo caso, restituisce Bar. In quest'ultimo, Baz. Se restituisce un valore Baz, assegnare un valore a una variabile Bar è un errore. Non sai quale sia, quindi non puoi assolutamente assumere nulla qui.

Considerare un metodo che ha un parametro T. Se si tratta di un GenericClass<Bar>, passarlo a Bar è legale. Se si tratta di un numero GenericClass<Baz>, passarlo a Bar è ancora legale perché Bar è un sottotipo di Baz.

Quindi, con un limite inferiore è possibile utilizzare parametri di metodo generici, ma non valori di ritorno generici.

In breve: <? extends T> significa che è possibile utilizzare valori di ritorno generici ma non parametri. <? super T> significa che è possibile utilizzare parametri generici ma non restituire valori. Predicate.test() ha un parametro generico, quindi è necessario super.

Un'altra cosa da considerare: i limiti indicati da un carattere jolly riguardano l'argomento di tipo effettivo dell'oggetto. Le loro conseguenze sui tipi che è possibile utilizzare conoggetto sono l'opposto. Un carattere jolly con limite superiore (extends) è un limite inferiore per i tipi di variabili a cui è possibile assegnare valori di ritorno. Un carattere jolly con limite inferiore (super) è un limite superiore dei tipi che è possibile passare come parametri. predicate.test(new Object()) non verrà compilato poiché, con un limite inferiore di String, accetta solo sottoclassi di String.

+0

Grazie per l'intuizione. Il tipo di ritorno/parametro riepilogo rende molto più facile capire il loro utilizzo. – piper1970

Problemi correlati