He estado observando la diferencia entre Collections.sort
y list.sort
, específicamente con respecto al uso de Comparator
mé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::getTitle
para 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 ArrayList
tipo 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 Song
al 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 thenComparing
llamadas siguientes . Sin embargo, Collections.sort
lo resuelve para el primero, pero no para el último. Probé, agregué varias llamadas adicionales thenComparing
y 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 TreeSet
y 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.compare
la última llamada a thenComparing
muestra 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 thenComparing
llamadas)?
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 Song
del 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, thenComparing
pero 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.sort
y Collections.sort
cuando elimino ese primer Song
tipo 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.
t1
yt2
en elObjects.compare
ejemplo? 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, porp1
lo 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 unthenComparing
mé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.comparing
no 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"
thenComparing
error 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é
List
está haciendo un mejor trabajo de inferencia queCollection
porque debería hacer el mismo tipo de captura, pero aparentemente no.fuente
ArrayList
para laCollections
solución pero no para la solución (dado que la primera llamada en la cadena tiene unSong
pará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