Muy confundido por Java 8 comparator type inference

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 comstackdor 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 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") ); 

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 syntax 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 comstackció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 set = new TreeSet( 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 comstackció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 set = new TreeSet( 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 syntax 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 syntax 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 comstack utilizando el comstackdor java JDK8 en la línea de comandos comstack OK.

Ordenar errores en Eclipse

En primer lugar, todos los ejemplos que dices causan errores en la comstackció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 generics 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 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 objective de Comparator , 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 de comparing() 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.comparing(...) .
  • Proporcione un tipo de objective explícito con un molde, al convertir la expresión del receptor a Comparator .

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:

  1. Use la nueva syntax de referencia del método Java 8

      Collections.sort(playlist, Comparator.comparing(Song::getTitle) .thenComparing(Song::getDuration) .thenComparing(Song::getArtist) ); 
  2. Saque cada paso de comparación en una referencia local

      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) ); 

    EDITAR

  3. 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.comparing((s) -> s.getTitle()) .thenComparing(p1 -> p1.getDuration()) .thenComparing(p1 -> p1.getArtist()) ); 

Creo que el “último” error de syntaxComparente te está engañando. En realidad, es un problema de tipo con toda la cadena, es solo el comstackdor que marca el final de la cadena como un error de syntax 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.

playlist1.sort(...) crea un límite de canción para la variable de tipo E, a partir de la statement 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 comstackdor deduzca el rest.

Creo que obtendrá un comportamiento “correcto” de Collections.sort(...) , pero no tiene una instalación de java 8 para probarlo.

Otra forma de lidiar con este error de tiempo de comstackció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 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()); 
    Intereting Posts