Qual é a diferença entre ? e objeto em Java genéricos?

137

Estou usando o Eclipse para me ajudar a limpar algum código para usar os genéricos Java corretamente. Na maioria das vezes, ele faz um excelente trabalho de inferir tipos, mas há alguns casos em que o tipo inferido deve ser o mais genérico possível: Objeto. Mas o Eclipse parece estar me dando uma opção para escolher entre um tipo de Objeto e um tipo de '?'.

Então, qual é a diferença entre:

HashMap<String, ?> hash1;

e

HashMap<String, Object> hash2;
skiphoppy
fonte
4
Veja o tutorial oficial sobre curingas . Ele explica bem e fornece um exemplo do porquê é necessário simplesmente usar o Object.
217 Ben S

Respostas:

148

Uma instância de HashMap<String, String>correspondências, Map<String, ?>mas não Map<String, Object>. Digamos que você queira escrever um método que aceite mapas de Strings para qualquer coisa: Se você escrever

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

você não pode fornecer a HashMap<String, String>. Se você escrever

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

funciona!

Uma coisa às vezes incompreendida nos genéricos de Java é que esse List<String>não é um subtipo de List<Object>. (Mas, String[]na verdade, é um subtipo de Object[], essa é uma das razões pelas quais genéricos e matrizes não se misturam bem. (Matrizes em Java são covariantes, genéricos não, são invariantes) )).

Exemplo: se você gostaria de escrever um método que aceite Lists de InputStreams e subtipos de InputStream, você escreveria

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

A propósito: O Java eficaz de Joshua Bloch é um excelente recurso quando você deseja entender as coisas não tão simples em Java. (Sua pergunta acima também é abordada muito bem no livro.)

Johannes Weiss
fonte
1
esta é a maneira correta de usar o ResponseEntity <?> no nível do controlador para todas as minhas funções do controlador?
Irakli
resposta perfeita Johannes!
gaurav
36

Outra maneira de pensar sobre esse problema é que

HashMap<String, ?> hash1;

é equivalente a

HashMap<String, ? extends Object> hash1;

Associe esse conhecimento ao "Princípio de obtenção e colocação " na seção (2.4) de Java Generics and Collections :

O princípio Obter e colocar: use um curinga estendido quando você obtém apenas valores de uma estrutura, use super curinga quando você coloca apenas valores em uma estrutura e não use um curinga quando obtém e coloca.

e o curinga pode começar a fazer mais sentido, espero.

Julien Chastang
fonte
1
E se "?" confunde você, "? estende Objeto" provavelmente o confundirá mais. Talvez.
Michael Myers
Tentando fornecer "ferramentas de pensamento" para permitir que se raciocine sobre esse assunto difícil. Forneceu informações adicionais sobre a extensão de curingas.
Julien Chastang
2
Obrigado pela informação adicional. Eu ainda estou digerindo. :)
skiphoppy
HashMap<String, ? extends Object> por isso, apenas impede nulla adição no hashmap?
mallaudin
12

É fácil entender se você se lembra de que Collection<Object>é apenas uma coleção genérica que contém objetos do tipo Object, mas Collection<?>é um supertipo de todos os tipos de coleções.

topchef
fonte
1
Há um argumento a ser feito de que isso não é exatamente fácil ;-), mas está certo.
23410 Sean Reilly
6

As respostas acima da covariância cobrem a maioria dos casos, mas perdem uma coisa:

"?" inclui "Objeto" na hierarquia de classes. Você poderia dizer que String é um tipo de Objeto e Objeto é um tipo de? Nem tudo corresponde ao objeto, mas tudo corresponde?

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
fonte
4

Você não pode colocar nada com segurança Map<String, ?>, porque não sabe que tipo de valores devem ser.

Você pode colocar qualquer objeto em um Map<String, Object>, porque o valor é conhecido como um Object.

erickson
fonte
"Você não pode colocar com segurança nada em Map <String,?>" False. Você pode, esse é o seu propósito.
217 Ben S
3
Ben está errado, o único valor que você pode colocar em uma coleção do tipo <?> É nulo, enquanto você pode colocar qualquer coisa em uma coleção do tipo <Object>.
sk.
2
Pelo link que dei na minha resposta: "Como não sabemos o que o tipo de elemento c representa, não podemos adicionar objetos a ele". Peço desculpas pela desinformação.
217 Ben Ben
1
o maior problema é que não entendo como é possível que você não possa adicionar nada ao HashMap <String,?>, se considerarmos que TUDO, exceto as primitivas, são objetos. Ou é para primitivos?
Avalon
1
@avalon Em outros lugares, há uma referência a esse mapa que é limitado. Por exemplo, pode ser um Map<String,Integer>. Somente Integerobjetos devem colocar armazenados no mapa como valores. Mas desde que você não sabe o tipo do valor (é ?), não sei se se é seguro para chamar put(key, "x"), put(key, 0)ou qualquer outra coisa.
31516
2

Declarar hash1como HashMap<String, ?>dita que a variável hash1pode conter qualquer uma HashMapque tenha uma chave Stringe qualquer tipo de valor.

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

Todas as opções acima são válidas, porque a variável map pode armazenar qualquer um desses mapas de hash. Essa variável não se importa com o tipo Value, do hashmap que ela contém.

Ter um curinga que não , no entanto, deixar você colocar qualquer tipo de objeto em seu mapa. de fato, com o mapa de hash acima, você não pode colocar nada usando a mapvariável:

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

Todas as chamadas de método acima resultarão em um erro em tempo de compilação porque o Java não sabe qual é o tipo Value do HashMap map.

Você ainda pode obter um valor do mapa de hash. Embora você "não saiba o tipo do valor" (porque não sabe que tipo de mapa de hash está dentro da sua variável), você pode dizer que tudo é uma subclasse Objecte, portanto, o que quer que você saia do mapa será do 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);

O bloco de código acima imprimirá 10 no console.


Portanto, para finalizar, use a HashMapcom curingas quando você não se importa (ou seja, não importa) quais são os tipos de HashMap, por exemplo:

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

Caso contrário, especifique os tipos que você precisa:

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

No método acima, precisaríamos saber que a chave do mapa é uma Character, caso contrário, não saberíamos que tipo usar para obter valores dela. Todos os objetos têm um toString()método, no entanto, para que o mapa possa ter qualquer tipo de objeto para seus valores. Ainda podemos imprimir os valores.

Kröw
fonte