He estado observando la diferencia entre Collections.sorty list.sort, específicamente con respecto al uso de Comparatormétodos estáticos y si se requieren tipos de parámetros en las expresiones lambda. Antes de comenzar, sé que podría usar referencias de métodos, por ejemplo, Song::getTitlepara superar mis problemas, pero mi consulta aquí no es tanto algo que quiero arreglar sino algo a lo que quiero una respuesta, es decir, ¿por qué el compilador de Java lo maneja de esta manera? .
Estos son mis hallazgos. Supongamos que tenemos un ArrayListtipo de Song, con algunas canciones agregadas, hay 3 métodos de obtención estándar:
ArrayList<Song> playlist1 = new ArrayList<Song>();
//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") );
Aquí hay una llamada a ambos tipos de método de clasificación que funciona, no hay problema:
Collections.sort(playlist1,
Comparator.comparing(p1 -> p1.getTitle()));
playlist1.sort(
Comparator.comparing(p1 -> p1.getTitle()));
Tan pronto como empiezo a encadenar thenComparing, sucede lo siguiente:
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())
);
es decir, errores de sintaxis porque ya no conoce el tipo de p1. Entonces, para solucionar esto, agrego el tipo Songal primer parámetro (de comparación):
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())
);
Ahora aquí viene la parte CONFUSA. Para p laylist1.sort, es decir, la Lista, esto resuelve todos los errores de compilación, para las dos thenComparingllamadas siguientes . Sin embargo, Collections.sortlo resuelve para el primero, pero no para el último. Probé, agregué varias llamadas adicionales thenComparingy siempre muestra un error para la última, a menos que puse (Song p1)el parámetro.
Ahora continué probando esto con la creación de un TreeSety con el uso Objects.compare:
int x = Objects.compare(t1, t2,
Comparator.comparing((Song p1) -> p1.getTitle())
.thenComparing(p1 -> p1.getDuration())
.thenComparing(p1 -> p1.getArtist())
);
Set<Song> set = new TreeSet<Song>(
Comparator.comparing((Song p1) -> p1.getTitle())
.thenComparing(p1 -> p1.getDuration())
.thenComparing(p1 -> p1.getArtist())
);
Ocurre lo mismo que en, para el TreeSet, no hay errores de compilación pero para Objects.comparela última llamada a thenComparingmuestra un error.
¿Alguien puede explicar por qué está sucediendo esto y también por qué no es necesario usar (Song p1)nada cuando simplemente se llama al método de comparación (sin más thenComparingllamadas)?
Otra consulta sobre el mismo tema es cuando hago esto con TreeSet:
Set<Song> set = new TreeSet<Song>(
Comparator.comparing(p1 -> p1.getTitle())
.thenComparing(p1 -> p1.getDuration())
.thenComparing(p1 -> p1.getArtist())
);
es decir, elimine el tipo Songdel primer parámetro lambda para la llamada al método de comparación, muestra errores de sintaxis bajo la llamada a comparar y la primera llamada a, thenComparingpero no a la llamada final thenComparing, ¡casi lo contrario de lo que estaba sucediendo arriba! Considerando que, para los otros 3 ejemplos, es decir Objects.compare, con List.sorty Collections.sortcuando elimino ese primer Songtipo de parámetro, muestra errores de sintaxis para todas las llamadas.
Muchas gracias de antemano.
Editado para incluir una captura de pantalla de los errores que recibía en Eclipse Kepler SR2, que desde entonces he descubierto que son específicos de Eclipse porque cuando se compila con el compilador java JDK8 en la línea de comandos, se compila correctamente.


t1yt2en elObjects.compareejemplo? Estoy tratando de inferirlos, pero superponer mi inferencia de tipo sobre la inferencia de tipo del compilador es intratable. :-)Respuestas:
Primero, todos los ejemplos que dice que causan errores se compilan bien con la implementación de referencia (javac de JDK 8.) También funcionan bien en IntelliJ, por lo que es muy posible que los errores que está viendo sean específicos de Eclipse.
Su pregunta subyacente parece ser: "¿por qué deja de funcionar cuando empiezo a encadenar?". La razón es que, mientras que las expresiones lambda y las invocaciones de métodos genéricos son poli-expresiones (su tipo es sensible al contexto) cuando aparecen como parámetros de método, cuando aparecen como expresiones de receptor de método, no lo son.
Cuando tu dices
hay suficiente información de tipo para resolver tanto el tipo de argumento
comparing()como el tipo de argumentop1. Lacomparing()llamada obtiene su tipo de destino de la firma deCollections.sort, por lo que se sabe quecomparing()debe devolver unComparator<Song>y, porp1lo tanto, debe serSong.Pero cuando empiezas a encadenar:
ahora tenemos un problema. Sabemos que la expresión compuesta
comparing(...).thenComparing(...)tiene un tipo objetivo deComparator<Song>, pero debido a que la expresión receptora para la cadena,,comparing(p -> p.getTitle())es una llamada a un método genérico, y no podemos inferir sus parámetros de tipo a partir de sus otros argumentos, no tenemos suerte. . Como no conocemos el tipo de esta expresión, no sabemos que tiene unthenComparingmétodo, etc.Hay varias formas de solucionar este problema, todas las cuales implican inyectar más información de tipo para que el objeto inicial de la cadena se pueda escribir correctamente. Aquí están, en orden aproximado de conveniencia decreciente y creciente intrusión:
Song::getTitle. Esto proporciona suficiente información de tipo para inferir las variables de tipo para lacomparing()llamada y, por lo tanto, darle un tipo y, por lo tanto, continuar en la cadena.comparing()llamada:Comparator.<Song, String>comparing(...).Comparator<Song>.fuente
Comparator.<Song, String>comparing(...).El problema es la inferencia de tipos. Sin agregar
(Song s)a la primera comparación,comparator.comparingno conoce el tipo de entrada, por lo que el valor predeterminado es Object.Puede solucionar este problema de 1 de 3 formas:
Utilice la nueva sintaxis de referencia del método Java 8
Extraiga cada paso de comparación en una referencia local
EDITAR
Forzar el tipo devuelto por el Comparador (tenga en cuenta que necesita tanto el tipo de entrada como el tipo de clave de comparación)
Creo que el "último"
thenComparingerror de sintaxis te está engañando. En realidad, es un problema de tipo con toda la cadena, es solo que el compilador solo marca el final de la cadena como un error de sintaxis porque es entonces cuando el tipo de retorno final no coincide, supongo.No estoy seguro de por qué
Listestá haciendo un mejor trabajo de inferencia queCollectionporque debería hacer el mismo tipo de captura, pero aparentemente no.fuente
ArrayListpara laCollectionssolución pero no para la solución (dado que la primera llamada en la cadena tiene unSongparámetro)?Otra forma de lidiar con este error de tiempo de compilación:
Emite la variable de tu primera función de comparación explícitamente y luego listo. He ordenado la lista de objetos org.bson.Documents. Mire el código de muestra
Comparator<Document> 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());fuente
playlist1.sort(...)crea un límite de Song para la variable de tipo E, desde la declaración de playlist1, que "ondula" al comparador.En
Collections.sort(...), no existe tal límite, y la inferencia del tipo del primer comparador no es suficiente para que el compilador infiera el resto.Creo que obtendrás un comportamiento "correcto"
Collections.<Song>sort(...), pero no tienes una instalación de Java 8 para probarlo.fuente