Molto confuso da inferenza di tipo Java 8 Comparator

Sono stato a guardare la differenza tra Collections.sort e list.sort , in particolare per quanto riguarda l’utilizzo dei metodi statici di Comparator e se i tipi param sono richiesti nelle espressioni lambda. Prima di iniziare, so che potrei usare i riferimenti al metodo, ad esempio Song::getTitle per superare i miei problemi, ma la mia query qui non è tanto qualcosa che voglio correggere ma qualcosa a cui voglio una risposta, cioè perché la gestione del compilatore Java in questo modo.

Queste sono le mie conclusioni. Supponiamo di avere una ArrayList di tipo Song , con alcune canzoni aggiunte, ci sono 3 metodi get standard:

  ArrayList playlist1 = new ArrayList(); //add some new Song objects playlist.addSong( new Song("Only Girl (In The World)", 235, "Rhianna") ); playlist.addSong( new Song("Thinking of Me", 206, "Olly Murs") ); playlist.addSong( new Song("Raise Your Glass", 202,"P!nk") ); 

Ecco una chiamata a entrambi i tipi di metodo di ordinamento che funziona, nessun problema:

 Collections.sort(playlist1, Comparator.comparing(p1 -> p1.getTitle())); playlist1.sort( Comparator.comparing(p1 -> p1.getTitle())); 

Non appena inizio a thenComparing , accade quanto segue:

 Collections.sort(playlist1, Comparator.comparing(p1 -> p1.getTitle()) .thenComparing(p1 -> p1.getDuration()) .thenComparing(p1 -> p1.getArtist()) ); playlist1.sort( Comparator.comparing(p1 -> p1.getTitle()) .thenComparing(p1 -> p1.getDuration()) .thenComparing(p1 -> p1.getArtist()) ); 

cioè errori di syntax perché non conosce più il tipo di p1 . Quindi per risolvere questo problema aggiungo il tipo Song al primo parametro (di confronto):

 Collections.sort(playlist1, Comparator.comparing((Song p1) -> p1.getTitle()) .thenComparing(p1 -> p1.getDuration()) .thenComparing(p1 -> p1.getArtist()) ); playlist1.sort( Comparator.comparing((Song p1) -> p1.getTitle()) .thenComparing(p1 -> p1.getDuration()) .thenComparing(p1 -> p1.getArtist()) ); 

Ora arriva la parte CONFUSING. Per p laylist1.sort , ovvero la List, questo risolve tutti gli errori di compilazione, sia per le seguenti chiamate thenComparing . Tuttavia, per Collections.sort , lo risolve per il primo, ma non per l’ultimo. Ho verificato l’aggiunta di diverse chiamate extra a thenComparing e mostra sempre un errore per l’ultimo, a meno che non abbia inserito (Song p1) per il parametro.

Ora ho continuato a testare ulteriormente con la creazione di un TreeSet e con l’uso di Objects.compare :

 int x = Objects.compare(t1, t2, Comparator.comparing((Song p1) -> p1.getTitle()) .thenComparing(p1 -> p1.getDuration()) .thenComparing(p1 -> p1.getArtist()) ); Set set = new TreeSet( Comparator.comparing((Song p1) -> p1.getTitle()) .thenComparing(p1 -> p1.getDuration()) .thenComparing(p1 -> p1.getArtist()) ); 

La stessa cosa accade come in, per TreeSet , non ci sono errori di compilazione ma per Objects.compare l’ultima chiamata a thenComparing mostra un errore.

Qualcuno può spiegare perché questo sta accadendo e anche perché non è necessario utilizzare (Song p1) quando si chiama semplicemente il metodo di confronto (senza ulteriori chiamate di confronto).

Un’altra query sullo stesso argomento è quando TreeSet questa operazione su TreeSet :

 Set set = new TreeSet( Comparator.comparing(p1 -> p1.getTitle()) .thenComparing(p1 -> p1.getDuration()) .thenComparing(p1 -> p1.getArtist()) ); 

cioè rimuovi il tipo Song dal primo parametro lambda per la chiamata al metodo comparativo, mostra gli errori di syntax sotto la chiamata al confronto e la prima chiamata a thenComparing ma non alla chiamata finale a thenComparing – quasi l’opposto di quello che stava succedendo sopra! Considerando che, per tutti gli altri 3 esempi, vale a dire con Objects.compare , List.sort e Collections.sort quando rimuovo quel tipo di parametro List.sort Song mostra errori di syntax per tutte le chiamate.

Molte grazie in anticipo.

Modificato per includere screenshot degli errori che stavo ricevendo in Eclipse Kepler SR2, che ora ho trovato sono specifici di Eclipse perché quando compilato usando il compilatore java JDK8 sulla riga di comando compila OK.

Ordina gli errori in Eclipse

Prima di tutto, tutti gli esempi che dici causano errori compilati bene con l’implementazione di riferimento (javac da JDK 8.) Funzionano anche bene in IntelliJ, quindi è abbastanza probabile che gli errori che stai vedendo siano specifici di Eclipse.

La tua domanda di fondo sembra essere: “perché smette di funzionare quando inizio a concatenare”. Il motivo è, mentre le espressioni lambda e le invocazioni di metodi generici sono espressioni poligonali (il loro tipo è sensibile al contesto) quando compaiono come parametri del metodo, quando invece appaiono come espressioni di ricezione del metodo, non lo sono.

Quando dici

 Collections.sort(playlist1, comparing(p1 -> p1.getTitle())); 

c’è abbastanza informazioni sul tipo da risolvere sia per l’argomento type di comparing() che per l’argomento type p1 . La chiamata a comparing() ottiene il suo tipo di destinazione dalla firma di Collections.sort , quindi è noto che il comparing() deve restituire un Comparator , e quindi p1 deve essere Song .

Ma quando inizi a incatenare:

 Collections.sort(playlist1, comparing(p1 -> p1.getTitle()) .thenComparing(p1 -> p1.getDuration()) .thenComparing(p1 -> p1.getArtist())); 

ora abbiamo un problema Sappiamo che l’espressione composta che comparing(...).thenComparing(...) ha un tipo di target di Comparator , ma perché l’espressione del ricevitore per la catena, comparing(p -> p.getTitle()) , è una chiamata al metodo generico, e non possiamo inferire i suoi parametri di tipo dagli altri argomenti, siamo piuttosto sfortunati. Dato che non conosciamo il tipo di questa espressione, non sappiamo che abbia un metodo thenComparing , ecc.

Ci sono diversi modi per risolvere questo problema, tutti implicano l’iniezione di più informazioni sul tipo in modo che l’object iniziale nella catena possa essere digitato correttamente. Eccoli, in ordine approssimativo di diminuzione della desiderabilità e crescente invadenza:

  • Usa un riferimento al metodo esatto (uno senza sovraccarichi), come Song::getTitle . Ciò fornisce quindi informazioni di tipo sufficienti per dedurre le variabili di tipo per la chiamata a comparing() , quindi fornire un tipo e quindi continuare lungo la catena.
  • Usa un lambda esplicito (come hai fatto nel tuo esempio).
  • Fornire un testimone di tipo per la chiamata a comparing() : Comparator.comparing(...) .
  • Fornire un tipo di target esplicito con un cast, convertendo l’espressione del destinatario in Comparator .

Il problema è inferire il tipo. Senza aggiungere una (Song s) al primo confronto, comparator.comparing non conosce il tipo di input in modo che sia impostato su Object.

Puoi risolvere questo problema in 1 o 3 modi:

  1. Utilizzare la nuova syntax del riferimento al metodo Java 8

      Collections.sort(playlist, Comparator.comparing(Song::getTitle) .thenComparing(Song::getDuration) .thenComparing(Song::getArtist) ); 
  2. Estrarre ogni fase di confronto in un riferimento locale

      Comparator byName = (s1, s2) -> s1.getArtist().compareTo(s2.getArtist()); Comparator byDuration = (s1, s2) -> Integer.compare(s1.getDuration(), s2.getDuration()); Collections.sort(playlist, byName .thenComparing(byDuration) ); 

    MODIFICARE

  3. Forzare il tipo restituito dal comparatore (nota che è necessario sia il tipo di input che il tipo di chiave di confronto)

     sort( Comparator.comparing((s) -> s.getTitle()) .thenComparing(p1 -> p1.getDuration()) .thenComparing(p1 -> p1.getArtist()) ); 

Penso che l’errore di syntax “last” thenComparing sia fuorviante. In realtà è un problema di tipo con l’intera catena, è solo il compilatore che segna la fine della catena come un errore di syntax perché è quando il tipo di ritorno finale non corrisponde, immagino.

Non sono sicuro del motivo per cui List sta facendo un lavoro di inferenza migliore rispetto a Collection poiché dovrebbe fare lo stesso tipo di cattura ma apparentemente no.

playlist1.sort(...) crea un bound di Song per la variabile di tipo E, dalla dichiarazione di playlist1, che “increspa” al comparatore.

In Collections.sort(...) , non esiste tale limite e l’inferenza dal tipo del primo comparatore non è sufficiente per il compilatore per dedurre il resto.

Penso che si otterrebbe un comportamento “corretto” da Collections.sort(...) , ma non si dispone di un’installazione di java 8 per testarlo.

Un altro modo per gestire questo errore in fase di compilazione:

Trasmetti la tua prima variabile comparativa della funzione in modo esplicito e poi buona per andare. Ho ordinato l’elenco dell’object org.bson.Documents. Si prega di guardare il codice di esempio

 Comparator comparator = Comparator.comparing((Document hist) -> (String) hist.get("orderLineStatus"), reverseOrder()) .thenComparing(hist -> (Date) hist.get("promisedShipDate")) .thenComparing(hist -> (Date) hist.get("lastShipDate")); list = list.stream().sorted(comparator).collect(Collectors.toList());