java8 - Muy confundido por Java 8 comparator type inference
list sort java (4)
El problema es el tipo de inferencia. Sin agregar una (Song s)
a la primera comparación, comparator.comparing
no conoce el tipo de entrada, por lo que su valor predeterminado es Object.
Puede solucionar este problema de 1 de 3 formas:
Use la nueva sintaxis de referencia del método Java 8
Collections.sort(playlist, Comparator.comparing(Song::getTitle) .thenComparing(Song::getDuration) .thenComparing(Song::getArtist) );
Saque cada paso de comparación en una referencia local
Comparator<Song> byName = (s1, s2) -> s1.getArtist().compareTo(s2.getArtist()); Comparator<Song> byDuration = (s1, s2) -> Integer.compare(s1.getDuration(), s2.getDuration()); Collections.sort(playlist, byName .thenComparing(byDuration) );
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)
sort( Comparator.<Song, String>comparing((s) -> s.getTitle()) .thenComparing(p1 -> p1.getDuration()) .thenComparing(p1 -> p1.getArtist()) );
Creo que el "último" error de sintaxisComparente te está engañando. En realidad, es un problema de tipo con toda la cadena, es solo el compilador que marca el final de la cadena como un error de sintaxis porque es cuando el tipo de retorno final no coincide, supongo.
No estoy seguro de por qué List
está haciendo un mejor trabajo de inferencia que Collection
ya que debería hacer el mismo tipo de captura, pero aparentemente no.
He estado observando la diferencia entre Collections.sort
y list.sort
, específicamente con respecto al uso de los métodos estáticos del Comparator
y si se requieren tipos de param en las expresiones lambda. Antes de comenzar, sé que podría utilizar referencias de métodos, por ejemplo, Song::getTitle
para superar mis problemas, pero mi consulta aquí no es tanto algo que quiera solucionar, sino algo a lo que quiero responder, es decir, ¿por qué se maneja el compilador Java? de esta manera.
Estos son mis hallazgos. Supongamos que tenemos un ArrayList
de tipo 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 y thenComparing
a 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 arreglar 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, tanto para las siguientes llamadas de thenComparing
. Sin embargo, para Collections.sort
, lo resuelve para el primero, pero no el último. thenComparing
agregar varias llamadas adicionales a thenComparing
y siempre muestra un error para la última, a menos que puse (Song p1)
para el parámetro.
Ahora TreeSet
a probar esto más a fondo con la creación de un TreeSet
y con el uso de 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())
);
Pasa lo mismo que en, para TreeSet
, no hay errores de compilación, pero para Objects.compare
la última llamada a thenComparing
muestra un error.
¿Alguien puede explicar por qué sucede esto y también por qué no hay necesidad de utilizar (Song p1)
en absoluto cuando simplemente se llama al método de comparación (sin más llamadas de thenComparing
).
Otra consulta sobre el mismo tema es cuando hago esto en el TreeSet
:
Set<Song> set = new TreeSet<Song>(
Comparator.comparing(p1 -> p1.getTitle())
.thenComparing(p1 -> p1.getDuration())
.thenComparing(p1 -> p1.getArtist())
);
es decir, eliminar el tipo Song
del primer parámetro lambda para la llamada al método de comparación, muestra los errores de sintaxis bajo la llamada a comparar y la primera llamada a thenComparing
pero no a la llamada final a thenComparing
- ¡casi lo contrario de lo que estaba sucediendo arriba! Mientras que, para los otros 3 ejemplos, es decir, con Objects.compare
, List.sort
y Collections.sort
cuando Objects.compare
ese primer tipo de param de Song
se muestran errores de sintaxis para todas las llamadas.
Muchas gracias de antemano.
Editado para incluir una captura de pantalla de los errores que estaba recibiendo en Eclipse Kepler SR2, que ahora he encontrado son específicos de Eclipse porque cuando se compila utilizando el compilador java JDK8 en la línea de comandos compila OK.
En primer lugar, todos los ejemplos que dices causan errores en la compilación de 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ás 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 expresiones poli (su tipo es sensible al contexto) cuando aparecen como parámetros de método, cuando aparecen en su lugar como expresiones de receptor de método, no lo son.
Cuando tu dices
Collections.sort(playlist1, comparing(p1 -> p1.getTitle()));
hay suficiente información de tipos para resolver tanto para el argumento tipo de comparing()
como para el tipo de argumento p1
. La llamada de comparing()
obtiene su tipo de destino de la firma de Collections.sort
, por lo que se conoce que comparing()
debe devolver un Comparator<Song>
y, por lo tanto, p1
debe ser Song
.
Pero cuando comienzas a encadenar:
Collections.sort(playlist1,
comparing(p1 -> p1.getTitle())
.thenComparing(p1 -> p1.getDuration())
.thenComparing(p1 -> p1.getArtist()));
ahora tenemos un problema Sabemos que la expresión compuesta que comparing(...).thenComparing(...)
tiene un tipo objetivo de Comparator<Song>
, pero debido a la expresión del receptor para la cadena, que comparing(p -> p.getTitle())
, es una llamada a un método genérico, y no podemos inferir sus parámetros de tipo de sus otros argumentos, estamos fuera de suerte. Como no conocemos el tipo de esta expresión, no sabemos si tiene el método de continuación, etc.
Hay varias formas de corregir esto, todas las cuales implican inyectar más información de tipo para que el objeto inicial en la cadena pueda escribirse correctamente. Aquí están, en orden aproximado de deseabilidad decreciente y creciente intromisión:
- Use una referencia de método exacta (una sin sobrecargas), como
Song::getTitle
. Esto proporciona suficiente información de tipo para inferir las variables de tipo para la llamada decomparing()
y, por lo tanto, le da un tipo y, por lo tanto, continúa hacia abajo de la cadena. - Use una lambda explícita (como lo hizo en su ejemplo).
- Proporcione un testigo de tipo para la llamada de
comparing()
:Comparator.<Song, String>comparing(...)
. - Proporcione un tipo de objetivo explícito con un molde, al convertir la expresión del receptor a
Comparator<Song>
.
Otra forma de lidiar con este error de tiempo de compilación:
Eche explícitamente la primera variable de la función de comparación y luego vaya bien. He ordenado la lista del objeto org.bson.Documents. Por favor 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());
playlist1.sort(...)
crea un límite de canción para la variable de tipo E, a partir de la declaración de playlist1, que se "ondula" al comparador.
En Collections.sort(...)
, no hay tal límite, y la inferencia del tipo del primer comparador no es suficiente para que el compilador deduzca el resto.
Creo que obtendrá un comportamiento "correcto" de Collections.<Song>sort(...)
, pero no tiene una instalación de java 8 para probarlo.