¿Por qué el uso de Collections.emptySet() con genéricos funciona en la asignación pero no como un parámetro de método?

5 minutos de lectura

Avatar de usuario de Karl von L
Karl von L.

Entonces, tengo una clase con un constructor como este:

public FilterList(Set<Integer> labels) {
    ...
}

y quiero construir una nueva FilterList objeto con un conjunto vacío. Siguiendo el consejo de Joshua Bloch en su libro Java efectivo, no quiero crear un nuevo objeto para el conjunto vacío; solo usaré Collections.emptySet() en cambio:

FilterList emptyList = new FilterList(Collections.emptySet());

Esto me da un error, quejándose de que java.util.Set<java.lang.Object> no es un java.util.Set<java.lang.Integer>. Bien, qué tal esto:

FilterList emptyList = new FilterList((Set<Integer>)Collections.emptySet());

¡Esto también me da un error! Bien, qué tal esto:

Set<Integer> empty = Collections.emptySet();
FilterList emptyList = new FilterList(empty);

¡Oye, funciona! ¿Pero por qué? Después de todo, Java no tiene inferencia de tipo, por lo que recibe una advertencia de conversión sin marcar si la tiene. Set<Integer> foo = new TreeSet() en vez de Set<Integer> foo = new TreeSet<Integer>(). Pero Set<Integer> empty = Collections.emptySet(); funciona sin siquiera una advertencia. ¿Porqué es eso?

  • todas las respuestas a continuación son correctas, pero lo que no entiendo es: ¿por qué inicializaría su colección con una lista vacía en lugar de llamar a un constructor predeterminado sin parámetros? Cada colección que conozco tiene un constructor con una colección existente y un constructor vacío.

    – Sean Patrick Floyd

    17 de junio de 2010 a las 13:35

  • Curiosamente, lo siguiente SÍ compila: FilterList emptyList = new FilterList((Set<Integer>)(Set<? extends Object>)Collections.emptySet());

    – EricS

    13 de septiembre de 2012 a las 18:43


  • Otro ejemplo bastante divertido: Set<Integer> emptySet = (Set<Integer>)Collections.emptySet(); no compila

    – neo

    27 de noviembre de 2013 a las 18:05

La respuesta corta es: esa es una limitación de la inferencia de tipos en el sistema genérico de Java. Puede inferir tipos genéricos contra variables concretas, pero no contra parámetros de método.

yo sospechar esto se debe a que los métodos se envían dinámicamente según la clase de tiempo de ejecución del objeto propietario, por lo que en tiempo de compilación (cuando todos se resuelve la información genérica) en realidad no puede saber con certeza cuál será la clase del parámetro del método y, por lo tanto, no puede inferir. Las declaraciones de variables son agradables y constantes, por lo que puede hacerlo.

Alguien más podría dar más detalles y/o un buen enlace. 🙂

En cualquier caso, siempre puede especificar los parámetros de tipo explícitamente para llamadas genéricas de esta manera:

Collections.<Integer>emptySet();

o incluso varios parámetros a la vez, por ejemplo

Collections.<String, Boolean>emptyMap(); // Returns a Map<String, Boolean>

Esto a menudo parece un poco más limpio que tener que lanzar, en los casos en que la inferencia no se activa.

  • +1 por una buena explicación del por qué. Desearía poder marcar dos respuestas correctas en SO 🙂

    – jdmichal

    17 de junio de 2010 a las 13:10

  • Sé que el compilador no mirará la invocación del método para el contexto al inferir, pero no estoy seguro de por qué. Al menos para uso privado o final métodos, debería ser posible hacer inferencias.

    – Hank Gay

    17 de junio de 2010 a las 13:23

  • @Hank: o static métodos, que también se resuelven en tiempo de compilación. Todavía – eso noque supongo que es el punto principal.

    –Andrzej Doyle

    17 de junio de 2010 a las 13:25

  • “Sospecho que esto se debe a que los métodos se envían dinámicamente según la clase de tiempo de ejecución del objeto propietario, por lo que en el momento de la compilación… no se puede saber con seguridad cuál será la clase del parámetro del método y, por lo tanto, no se puede inferir .” En ese caso, ¿por qué no funciona la versión con el tipo de conversión?

    – EricS

    13 de septiembre de 2012 a las 18:39


  • “Sospecho que esto se debe a que los métodos se envían dinámicamente según la clase de tiempo de ejecución del objeto propietario”. No estoy seguro de si es lo mismo, pero la versión de un método sobrecargado para llamar se determina en el momento de la compilación: ampersand.space/blog/2007/1/12/java-overload

    – Kip

    16 mayo 2016 a las 14:57


Avatar de usuario de Andrei Fierbinteanu
Andrei Fierbinteanu

probar

FilterList emptyList = new FilterList(Collections.<Integer>emptySet());

Puede forzar el parámetro de tipo para los métodos que los tienen, en los casos en que la inferencia no sea lo suficientemente buena, o para permitirle usar subtipos; por ejemplo:

// forces use of ArrayList as parameter instead of the infered List
List<String> l = someObject.<ArrayList<String> methodThatTakesTypeParamForReturnType();

avatar de usuario de jdmichal
jdmichal

Quieres hacer esto:

FilterList emptyList = new FilterList(java.util.Collections.<Integer>emptySet());

Eso le dice a la emptySet método que su parámetro genérico debería explícitamente por Integer en lugar del predeterminado Object. Y sí, la sintaxis es completamente original y no intuitiva para esto. 🙂

  • No conocía ese syntex, gracias! Pero todavía me pregunto por qué esa sintaxis no es necesaria en la asignación de variables.

    – Karl von L.

    17 de junio de 2010 a las 13:10

  • Ver la respuesta de Andrzej Doyle. Creo que es una buena explicación.

    – jdmichal

    17 de junio de 2010 a las 13:12

  • no necesitas new allá. De hecho, no creo que se compile con él.

    – Andrei Fierbinteanu

    17 de junio de 2010 a las 13:29

Java tiene una inferencia de tipo, es bastante limitada. Si está interesado en saber exactamente cómo funciona y cuáles son sus limitaciones, esta es una muy buena lectura:

http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html#Type%2BArgument%2BInference

¿Ha sido útil esta solución?