Cuál es la diferencia entre ? y Objeto en genéricos de Java?

137

Estoy usando Eclipse para ayudarme a limpiar algo de código para usar los genéricos de Java correctamente. La mayoría de las veces está haciendo un excelente trabajo de inferir tipos, pero hay algunos casos en los que el tipo inferido tiene que ser lo más genérico posible: Objeto. Pero Eclipse parece estar dándome una opción para elegir entre un tipo de Objeto y un tipo de '?'.

Entonces, ¿cuál es la diferencia entre:

HashMap<String, ?> hash1;

y

HashMap<String, Object> hash2;
skiphoppy
fuente
44
Vea el tutorial oficial sobre comodines . Lo explica bien y da un ejemplo de por qué es necesario simplemente usando Object.
Ben S

Respuestas:

148

Una instancia de HashMap<String, String>partidos Map<String, ?>pero no Map<String, Object>. Digamos que desea escribir un método que acepte mapas de Strings a cualquier cosa: si pudiera escribir

public void foobar(Map<String, Object> ms) {
    ...
}

no puedes suministrar a HashMap<String, String>. Si tú escribes

public void foobar(Map<String, ?> ms) {
    ...
}

¡funciona!

Una cosa a veces mal entendida en los genéricos de Java es que List<String>no es un subtipo de List<Object>. (Pero, String[]de hecho, es un subtipo de Object[], esa es una de las razones por las que los genéricos y las matrices no se mezclan bien. (Las matrices en Java son covariantes, los genéricos no lo son, son invariantes )).

Muestra: si desea escribir un método que acepte Lists de InputStreamsy subtipos de InputStream, escribiría

public void foobar(List<? extends InputStream> ms) {
    ...
}

Por cierto: Java eficaz de Joshua Bloch es un excelente recurso cuando desea comprender las cosas no tan simples en Java. (Su pregunta anterior también se cubre muy bien en el libro).

Johannes Weiss
fuente
1
¿Es esta la forma correcta de utilizar ResponseEntity <?> en el nivel del controlador para todas las funciones de mi controlador?
Irakli
respuesta impecable Johannes!
gaurav
36

Otra forma de pensar sobre este problema es que

HashMap<String, ?> hash1;

es equivalente a

HashMap<String, ? extends Object> hash1;

Combine este conocimiento con el "Principio Get and Put" en la sección (2.4) de Java Generics and Collections :

El principio Get and Put: use un comodín extendido cuando solo obtenga valores de una estructura, use un súper comodín cuando solo ponga valores en una estructura, y no use un comodín cuando obtenga y ponga.

y el comodín puede comenzar a tener más sentido, con suerte.

Julien Chastang
fuente
1
Si "?" te confunde, "? extiende Objeto" probablemente te confundirá más. Tal vez.
Michael Myers
Tratando de proporcionar "herramientas de pensamiento" para que uno pueda razonar sobre este tema difícil. Proporcionó información adicional sobre la extensión de comodines.
Julien Chastang el
2
Gracias por la información adicional. Todavía lo estoy digiriendo. :)
skiphoppy
HashMap<String, ? extends Object> así que solo evita nullque se agregue en el hashmap?
mallaudin
12

Es fácil de entender si recuerda que Collection<Object>es solo una colección genérica que contiene objetos de tipo Object, pero Collection<?>es un súper tipo de todos los tipos de colecciones.

el mejor chef
fuente
1
Hay que señalar que esto no es exactamente fácil ;-), pero es correcto.
Sean Reilly
6

Las respuestas anteriores a la covarianza cubren la mayoría de los casos, pero pierden una cosa:

"?" incluye "Objeto" en la jerarquía de clases. Se podría decir que String es un tipo de objeto y Object es un tipo de?. ¿No todo coincide con el objeto, pero todo coincide?

int test1(List<?> l) {
  return l.size();
}

int test2(List<Object> l) {
  return l.size();
}

List<?> l1 = Lists.newArrayList();
List<Object> l2 = Lists.newArrayList();
test1(l1);  // compiles because any list will work
test1(l2);  // compiles because any list will work
test2(l1);  // fails because a ? might not be an Object
test2(l2);  // compiled because Object matches Object
Eyal
fuente
4

No puede poner nada con seguridad Map<String, ?>, porque no sabe de qué tipo se supone que son los valores.

Puede poner cualquier objeto en a Map<String, Object>, porque se sabe que el valor es un Object.

erickson
fuente
"No puedes poner nada con seguridad en el Mapa <Cadena,?>" Falso. PUEDES, ese es su propósito.
Ben S
3
Ben está equivocado, el único valor que puede poner en una colección de tipo <?> Es nulo, mientras que puede poner cualquier cosa en una colección de tipo <Object>.
sk.
2
Desde el enlace que di en mi respuesta: "Dado que no sabemos qué significa el tipo de elemento de c, no podemos agregarle objetos". Pido disculpas por la desinformación.
Ben S
1
el mayor problema es que no entiendo cómo es posible que no puedas agregar nada a HashMap <String,?>, si consideramos que TODO, excepto las primitivas, son objetos. ¿O es para los primitivos?
avalon
1
@avalon En otra parte, hay una referencia a ese mapa que está delimitada. Por ejemplo, podría ser un Map<String,Integer>. Solo los Integerobjetos deben colocarse almacenados en el mapa como valores. Pero ya que no se conoce el tipo del valor (es ?), no se sabe si si es seguro para llamar put(key, "x"), put(key, 0)o cualquier otra cosa.
erickson
2

Declarar hash1como un HashMap<String, ?>dictamen que la variable hash1puede contener cualquiera HashMapque tenga una clave Stringy cualquier tipo de valor.

HashMap<String, ?> map;
map = new HashMap<String, Integer>();
map = new HashMap<String, Object>();
map = new HashMap<String, String>();

Todo lo anterior es válido, porque la variable map puede almacenar cualquiera de esos mapas hash. A esa variable no le importa cuál es el tipo de Valor, del hashmap que contiene.

Sin embargo, tener un comodín no le permite poner ningún tipo de objeto en su mapa. De hecho, con el mapa de hash anterior, no se puede agregar nada usando la mapvariable:

map.put("A", new Integer(0));
map.put("B", new Object());
map.put("C", "Some String");

Todas las llamadas a métodos anteriores generarán un error en tiempo de compilación porque Java no sabe cuál es el tipo de valor de HashMap en su interior map.

Todavía puede obtener un valor del mapa hash. Aunque "no conoce el tipo de valor" (porque no sabe qué tipo de mapa hash está dentro de su variable), puede decir que todo es una subclase de Objecty, por lo tanto, lo que sea que salga del mapa será del tipo Objeto:

HashMap<String, Integer> myMap = new HashMap<>();// This variable is used to put things into the map.

myMap.put("ABC", 10);

HashMap<String, ?> map = myMap;
Object output = map.get("ABC");// Valid code; Object is the superclass of everything, (including whatever is stored our hash map).

System.out.println(output);

El bloque de código anterior imprimirá 10 en la consola.


Por lo tanto, para terminar, utilice un HashMapcon comodines cuando no le importe (es decir, no importa) cuáles son los tipos de HashMap, por ejemplo:

public static void printHashMapSize(Map<?, ?> anyMap) {
    // This code doesn't care what type of HashMap is inside anyMap.
    System.out.println(anyMap.size());
}

De lo contrario, especifique los tipos que necesita:

public void printAThroughZ(Map<Character, ?> anyCharacterMap) {
    for (int i = 'A'; i <= 'Z'; i++)
        System.out.println(anyCharacterMap.get((char) i));
}

En el método anterior, necesitaríamos saber que la clave del Mapa es a Character, de lo contrario, no sabríamos qué tipo usar para obtener valores de ella. toString()Sin embargo, todos los objetos tienen un método, por lo que el mapa puede tener cualquier tipo de objeto para sus valores. Todavía podemos imprimir los valores.

Kröw
fuente