Qual è la differenza tra ? e oggetto in generici Java?


137

Sto usando Eclipse per aiutarmi a ripulire del codice per usare correttamente i generici Java. Il più delle volte sta facendo un ottimo lavoro di inferimento dei tipi, ma ci sono alcuni casi in cui il tipo inferito deve essere il più generico possibile: Oggetto. Ma Eclipse sembra offrirmi un'opzione per scegliere tra un tipo di oggetto e un tipo di "?".

Quindi qual è la differenza tra:

HashMap<String, ?> hash1;

e

HashMap<String, Object> hash2;

4
Guarda il tutorial ufficiale su Wildcard . Lo spiega bene e fornisce un esempio del perché è necessario semplicemente usando Object.
Ben S

Risposte:


148

Un'istanza di HashMap<String, String>partite Map<String, ?>ma non Map<String, Object>. Supponi di voler scrivere un metodo che accetti le mappe da Stringqualsiasi cosa: se scrivessi

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

non puoi fornire a HashMap<String, String>. Se scrivi

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

Funziona!

Una cosa a volte fraintesa nei generici di Java è che List<String>non è un sottotipo di List<Object>. (Ma String[]in realtà è un sottotipo di Object[], questo è uno dei motivi per cui i generici e gli array non si mescolano bene (gli array in Java sono covarianti, i generici no, sono invarianti )).

Esempio: se desideri scrivere un metodo che accetta Lists di InputStreams e sottotipi di InputStream, dovresti scrivere

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

A proposito: l'efficace Java di Joshua Bloch è una risorsa eccellente quando vuoi capire le cose non così semplici in Java. (Anche la tua domanda sopra è trattata molto bene nel libro.)


1
è questo il modo giusto di utilizzare ResponseEntity <?> a livello di controller per tutte le funzioni del mio controller?
Irakli,

risposta impeccabile Johannes!
Gaurav,

36

Un altro modo di pensare a questo problema è quello

HashMap<String, ?> hash1;

è equivalente a

HashMap<String, ? extends Object> hash1;

Associare questa conoscenza con il "Get and Put Principle" nella sezione (2.4) di Java Generics and Collections :

Il principio Get and Put: usa un jolly di estensione quando ottieni valori da una struttura, usa il super jolly quando inserisci valori solo in una struttura e non utilizzare un jolly quando ottieni e metti entrambi.

e il jolly può iniziare a dare più senso, si spera.


1
Se "?" ti confonde, "? estende Oggetto" probabilmente ti confonderà di più. Può essere.
Michael Myers

Cercare di fornire "strumenti di pensiero" per consentire di ragionare su questo argomento difficile. Informazioni aggiuntive sull'estensione dei caratteri jolly.
Julien Chastang,

2
Grazie per le informazioni aggiuntive. Lo sto ancora digerendo. :)
skiphoppy

HashMap<String, ? extends Object> quindi impedisce solo nulldi essere aggiunto nella hashmap?
Mallaudin,

12

È facile capire se ricordi che Collection<Object>è solo una raccolta generica che contiene oggetti di tipo Object, ma Collection<?>è un super tipo di tutti i tipi di raccolte.


1
C'è un punto da sottolineare che questo non è esattamente facile ;-), ma è giusto.
Sean Reilly,

6

Le risposte sopra covarianza coprono la maggior parte dei casi ma mancano una cosa:

"?" è comprensivo di "Oggetto" nella gerarchia di classi. Si potrebbe dire che String è un tipo di oggetto e Object è un tipo di?. Non tutto corrisponde all'oggetto, ma tutto corrisponde?

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

4

Non puoi inserire nulla in modo sicuro Map<String, ?>, perché non sai quale tipo dovrebbero essere i valori.

Puoi inserire qualsiasi oggetto in a Map<String, Object>, perché il valore è noto per essere un Object.


"Non puoi mettere nulla in modo sicuro in Map <String,?>" Falso. PUOI, questo è il suo scopo.
Ben S

3
Ben ha torto, l'unico valore che puoi inserire in una raccolta di tipo <?> È null, mentre puoi inserire qualsiasi cosa in una raccolta di tipo <Oggetto>.
sk.

2
Dal link che ho dato nella mia risposta: "Dato che non sappiamo quale sia il tipo di elemento di c, non possiamo aggiungere oggetti ad esso". Mi scuso per la disinformazione.
Ben S

1
il problema più grande è che non capisco come sia possibile che non sia possibile aggiungere nulla in HashMap <String,?>, se si considera che TUTTO tranne i primitivi sono oggetti. O è per i primitivi?
avalon,

1
@avalon Altrove, c'è un riferimento a quella mappa che è delimitata. Ad esempio, potrebbe essere un Map<String,Integer>. Solo gli Integeroggetti devono essere archiviati nella mappa come valori. Ma dal momento che non si conosce il tipo di valore (è ?), non si sa se se è sicuro di chiamare put(key, "x"), put(key, 0)o qualsiasi altra cosa.
Erickson,

2

Dichiarare hash1come un HashMap<String, ?>dettato che la variabile hash1può contenere qualsiasi HashMapche abbia una chiave diString e qualsiasi tipo di valore.

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

Tutto quanto sopra è valido, perché il variabile map può memorizzare una di quelle mappe hash. A quella variabile non importa quale sia il tipo di Valore, dell'hashmap che contiene.

Avere un carattere jolly no consente tuttavia di inserire alcun tipo di oggetto nella tua mappa. infatti, con la mappa hash sopra, non puoi inserire nulla usando la mapvariabile:

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

Tutte le chiamate al metodo sopra riportate comporteranno un errore in fase di compilazione perché Java non sa quale sia il tipo di valore all'interno di HashMap map.

Puoi ancora ottenere un valore dalla mappa hash. Anche se "non conosci il tipo di valore" (perché non sai quale tipo di mappa hash è all'interno della tua variabile), puoi dire che tutto è una sottoclasse di Object, e quindi qualunque cosa tu esca dalla mappa sarà del tipo Oggetto:

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

Il blocco di codice sopra verrà stampato 10 sulla console.


Quindi, per finire, usa un HashMapcarattere jolly quando non ti interessa (cioè non importa) quali sono i tipi di HashMap, ad esempio:

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

Altrimenti, specifica i tipi di cui hai bisogno:

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

Nel metodo sopra, dovremmo sapere che la chiave della mappa è un Character, altrimenti non sapremmo quale tipo usare per ottenere valori da essa. Tutti gli oggetti hanno un toString()metodo, tuttavia, quindi la mappa può avere qualsiasi tipo di oggetto per i suoi valori. Possiamo ancora stampare i valori.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.