.Min () e .max () di Java 8 stream: perché viene compilato?


215

Nota: questa domanda proviene da un link non attivo che era una precedente domanda SO, ma qui va ...

Vedi questo codice ( nota: so che questo codice non "funzionerà" e che Integer::comparedovrebbe essere usato - l'ho appena estratto dalla domanda collegata ):

final ArrayList <Integer> list 
    = IntStream.rangeClosed(1, 20).boxed().collect(Collectors.toList());

System.out.println(list.stream().max(Integer::max).get());
System.out.println(list.stream().min(Integer::min).get());

Secondo il javadoc di .min()e .max(), l'argomento di entrambi dovrebbe essere a Comparator. Eppure qui i riferimenti ai metodi sono metodi statici della Integerclasse.

Quindi, perché questo compila affatto?


6
Si noti che non funziona correttamente, dovrebbe essere utilizzato al Integer::compareposto di Integer::maxe Integer::min.
Christoffer Hammarström,

@ ChristofferHammarström Lo so; nota come ho detto prima dell'estratto del codice "Lo so, è assurdo"
fge

3
Non stavo cercando di correggerti, lo sto dicendo alla gente in generale. Lo hai fatto sembrare come se pensassi che la parte assurda è che i metodi di Integernon sono metodi di Comparator.
Christoffer Hammarström,

Risposte:


242

Lasciami spiegare cosa sta succedendo qui, perché non è ovvio!

Innanzitutto, Stream.max()accetta un'istanza in Comparatormodo che gli elementi nel flusso possano essere confrontati tra loro per trovare il minimo o il massimo, in un ordine ottimale di cui non devi preoccuparti troppo.

Quindi la domanda è, ovviamente, perché è Integer::maxaccettata? Dopotutto non è un comparatore!

La risposta è nel modo in cui la nuova funzionalità lambda funziona in Java 8. Si basa su un concetto che è noto in modo informale come interfacce "single abstract method" o interfacce "SAM". L'idea è che qualsiasi interfaccia con un metodo astratto può essere implementata automaticamente da qualsiasi lambda - o riferimento al metodo - la cui firma del metodo corrisponde a un metodo dell'interfaccia. Quindi esaminando l' Comparatorinterfaccia (versione semplice):

public Comparator<T> {
    T compare(T o1, T o2);
}

Se un metodo cerca un Comparator<Integer>, allora essenzialmente cerca questa firma:

int xxx(Integer o1, Integer o2);

Uso "xxx" perché il nome del metodo non viene utilizzato per scopi corrispondenti .

Pertanto, entrambi Integer.min(int a, int b)e Integer.max(int a, int b)sono abbastanza vicini che l'autoboxing consentirà di apparire come Comparator<Integer>in un contesto di metodo.


28
o in alternativa: list.stream().mapToInt(i -> i).max().get().
Assylias,

13
@assylias Vuoi usare .getAsInt()invece di get(), dato che hai a che fare con un OptionalInt.
Skiwi,

... quando stiamo solo cercando di fornire un comparatore personalizzato a una max()funzione!
Manu343726,

Vale la pena notare che questa "interfaccia SAM" è in realtà chiamata "interfaccia funzionale" e che guardando la Comparatordocumentazione possiamo vedere che è decorata con l'annotazione @FunctionalInterface. Questo decoratore è la magia che permette Integer::maxe Integer::mindi essere convertito in un Comparator.
Chris Kerekes,

2
@ChrisKerekes il decoratore @FunctionalInterfaceè principalmente a scopo di documentazione, poiché il compilatore può farlo felicemente con qualsiasi interfaccia con un solo metodo astratto.
errantlinguist,

117

Comparatorè un'interfaccia funzionale e Integer::maxconforme a tale interfaccia (dopo aver preso in considerazione l'autoboxing / unboxing). Prende due intvalori e restituisce un int- proprio come ci si aspetterebbe un Comparator<Integer>(di nuovo, strizzando gli occhi per ignorare la differenza Intero / int).

Tuttavia, non mi aspetto che faccia la cosa giusta, dato che Integer.maxnon è conforme alla semantica di Comparator.compare. E in effetti non funziona davvero in generale. Ad esempio, apporta una piccola modifica:

for (int i = 1; i <= 20; i++)
    list.add(-i);

... e ora il maxvalore è -20 e il minvalore è -1.

Invece, entrambe le chiamate dovrebbero usare Integer::compare:

System.out.println(list.stream().max(Integer::compare).get());
System.out.println(list.stream().min(Integer::compare).get());

1
Conosco le interfacce funzionali; e so perché dà i risultati sbagliati. Mi chiedo solo come mai il compilatore non mi ha
urlato contro

6
@fge: Beh, è ​​stato il unboxing di cui non eri chiaro? (Non ho esaminato esattamente quella parte.) A Comparator<Integer>avrebbe int compare(Integer, Integer)... non è sbalorditivo che Java permetta a un riferimento di metodo di int max(int, int)convertirlo in quello ...
Jon Skeet

7
@fge: il compilatore dovrebbe conoscere la semantica di Integer::max? Dal suo punto di vista hai passato una funzione che soddisfaceva le sue specifiche, questo è tutto ciò che può davvero continuare.
Mark Peters,

6
@fge: In particolare, se capisci parte di ciò che sta succedendo, ma sei incuriosito da un aspetto specifico di esso, vale la pena chiarirlo nella domanda per evitare che le persone perdano tempo a spiegare le parti che già conosci.
Jon Skeet,

20
Penso che il problema di fondo sia la firma del tipo di Comparator.compare. Dovrebbe restituire un enumdi {LessThan, GreaterThan, Equal}, non un int. In questo modo, l'interfaccia funzionale non corrisponderebbe effettivamente e si otterrebbe un errore di compilazione. IOW: la firma del tipo di Comparator.comparenon cattura adeguatamente la semantica di cosa significhi confrontare due oggetti, e quindi altre interfacce che non hanno assolutamente nulla a che fare con il confronto di oggetti hanno accidentalmente la stessa firma di tipo.
Jörg W Mittag,

19

Questo funziona perché si Integer::minrisolve in un'implementazione Comparator<Integer>dell'interfaccia.

Il riferimento al metodo Integer::minrisolve Integer.min(int a, int b), risolto IntBinaryOperatore presumibilmente autoboxing si verifica da qualche parte rendendolo un BinaryOperator<Integer>.

E i metodi di min()resp richiedono di implementare l' interfaccia. Ora questo si risolve nel singolo metodo . Che è di tipo .max()Stream<Integer>Comparator<Integer>
Integer compareTo(Integer o1, Integer o2)BinaryOperator<Integer>

E così la magia è avvenuta in quanto entrambi i metodi sono a BinaryOperator<Integer>.


Non è del tutto corretto dire che Integer::minimplementa Comparable. Non è un tipo in grado di implementare nulla. Ma viene valutato in un oggetto che implementa Comparable.
Lii,

1
@Lii Grazie, l'ho risolto proprio ora.
Skiwi,

Comparator<Integer>è un'interfaccia single-abstract-method (aka "funzionale") e Integer::minsoddisfa il suo contratto, quindi la lambda può essere interpretata come questa. Non so come vedi BinaryOperator entrare in gioco qui (o IntBinaryOperator) - non esiste alcuna relazione di sottotipo tra questo e Comparator.
Paŭlo Ebermann,

2

Oltre alle informazioni fornite da David M. Lloyd, si potrebbe aggiungere che il meccanismo che lo consente si chiama tipizzazione target .

L'idea è che il tipo che il compilatore assegna a espressioni lambda o riferimenti a un metodo non dipende solo dall'espressione stessa, ma anche da dove viene utilizzato.

Il target di un'espressione è la variabile a cui è assegnato il suo risultato o il parametro a cui è passato il suo risultato.

Alle espressioni lambda e ai riferimenti ai metodi viene assegnato un tipo che corrisponde al tipo di destinazione, se tale tipo può essere trovato.

Vedere la sezione Inferenza del tipo nel Tutorial Java per ulteriori informazioni.


1

Ho avuto un errore con un array che otteneva il massimo e il minimo, quindi la mia soluzione era:

int max = Arrays.stream(arrayWithInts).max().getAsInt();
int min = Arrays.stream(arrayWithInts).min().getAsInt();
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.