Domanda

Queste domande sono poste dal strano comportamento di HashMap.put ()

Penso di aver capito perché Map<K,V>.put prende un K ma Map<K,V>.get prende un Object, sembra che non lo faccia, si romperà troppo il codice esistente.

Ora entriamo in uno scenario molto soggetto a errori:

java.util.HashMap<Long, String> m = new java.util.HashMap<Long, String>();
m.put(5L,"Five"); // compiler barfs on m.put(5, "Five")
m.contains(5); // no complains from compiler, but returns false

Non è stato possibile risolvere questo problema restituendo true se il Long valore era compreso nell'intervallo int e i valori sono uguali?

È stato utile?

Soluzione

Ecco la fonte di Long.java

public boolean equals(Object obj) {
    if (obj instanceof Long) {
        return value == ((Long)obj).longValue();
    }
    return false;
}

vale a dire. deve essere un tipo Long per essere uguale. Penso che la differenza chiave tra:

long l = 42L
int i = 42;
l == i

e il tuo esempio sopra è che con le primitive può verificarsi un allargamento implicito del valore int, tuttavia con i tipi di oggetto non ci sono regole per la conversione implicita da Integer in Long.

Dai un'occhiata anche a Java Puzzlers , ha molti esempi simili a questo.

Altri suggerimenti

In generale, sebbene non sia strettamente espresso nel contratto per equals () , gli oggetti non devono considerarsi uguali a un altro oggetto che non sia della stessa identica classe (anche se si tratta di una sottoclasse). Considera la proprietà simmetrica - se a.equals (b) è vero, allora anche b.equals (a) deve essere vero.

Diamo due oggetti, foo di classe Super e bar di classe Sub, che si estende equals(). Ora considera l'implementazione di foo.equals(bar) in Super, in particolare quando viene chiamato come Object. Foo sa solo che la barra è fortemente tipizzata come HashSet<Sub>, quindi per ottenere un confronto accurato è necessario verificare che sia un'istanza di Super e se non restituire false. Lo è, quindi questa parte va bene. Ora confronta tutti i campi dell'istanza, ecc. (O qualunque sia l'implementazione effettiva del confronto) e li trova uguali. Fin qui tutto bene.

Tuttavia, dal contratto può restituire vero solo se sa che anche bar.equals (pippo) restituirà vero. Poiché la barra può essere una sottoclasse di Super, non è chiaro se il metodo equals () verrà sovrascritto (e se probabilmente lo sarà). Pertanto, per essere sicuri che la tua implementazione sia corretta, devi scriverla simmetricamente e assicurarti che i due oggetti siano della stessa classe.

Più fondamentalmente, gli oggetti di classi diverse non possono davvero essere considerati uguali - poiché in questo caso, solo una di esse può essere inserita in un <=>, ad esempio.

Sì, ma tutto dipende dall'algoritmo di confronto e da quanto lontano prendere le conversioni. Ad esempio, cosa vuoi che accada quando provi m.Contains("5")? O se gli passi un array con 5 come primo elemento? In parole povere, sembra essere cablato & Quot; se i tipi sono diversi, i tasti sono diversi & Quot ;.

Quindi prendi una collezione con un object come chiave. Cosa vuoi che accada se put a 5L, quindi cerchi di ottenere 5, "5", ...? Cosa succede se 5F un int e un long e un short e si desidera verificare la presenza di un float?

Poiché si tratta di una raccolta generica (o di un modello, o come si desidera chiamarla), dovrebbe verificare e fare un confronto speciale per determinati tipi di valore. Se K è double, controlla se l'oggetto passato è <=>, <=>, <=>, <=>, ..., quindi converti e confronta. Se K è <=>, controlla se l'oggetto passato è ...

Ottieni il punto.

Un'altra implementazione avrebbe potuto essere quella di generare un'eccezione se i tipi non corrispondessero, tuttavia, e spesso vorrei che lo facesse.

La tua domanda sembra ragionevole sulla sua faccia, ma sarebbe una violazione delle convenzioni generali per equals (), se non il suo contratto, restituire vero per due diversi tipi.

Parte del design del linguaggio Java prevedeva che gli oggetti non si convertissero mai implicitamente in altri tipi, diversamente dal C ++. Questo faceva parte del rendere Java un linguaggio piccolo e semplice. Una parte ragionevole della complessità di C ++ deriva dalle conversioni implicite e dalle loro interazioni con altre funzionalità.

Inoltre, Java ha una dicotomia nitida e visibile tra primitivi e oggetti. Ciò è diverso dalle altre lingue in cui questa differenza è nascosta sotto le copertine come ottimizzazione. Ciò significa che non puoi aspettarti che Long e Integer si comportino come lunghi e int.

Il codice della libreria può essere scritto per nascondere queste differenze, ma ciò può effettivamente nuocere rendendo l'ambiente di programmazione meno coerente.

Quindi il tuo codice dovrebbe essere ....

java.util.HashMap<Long, String> m = new java.util.HashMap<Long, String>();
m.put(5L, "Five"); // compiler barfs on m.put(5, "Five")
System.out.println(m.containsKey(5L)); // true

Stai dimenticando che java sta autoboxando il tuo codice, quindi il codice sopra sarebbe equivelenet su

java.util.HashMap<Long, String> m = new java.util.HashMap<Long, String>();
m.put(new Long(5L), "Five"); // compiler barfs on m.put(5, "Five")
System.out.println(m.containsKey(new Long(5))); // true
System.out.println(m.containsKey(new Long(5L))); // true

Quindi una parte del tuo problema è l'autoboxing. L'altra parte è che hai diversi tipi come altri poster hanno affermato.

Le altre risposte spiegano adeguatamente perché non funziona, ma nessuna di esse si occupa di come scrivere codice meno soggetto a errori su questo problema. Dover ricordare di aggiungere i cast di tipo (nessun aiuto del compilatore), aggiungere i primitivi con L e così via non è accettabile IMHO.

Consiglio vivamente di usare la libreria di raccolte GNU trove quando si hanno primitive (e in molti altri casi). Ad esempio, esiste una TLongLongHashMap che memorizza le cose all'interno come long primitivi. Di conseguenza, non si finisce mai con la boxe / unboxing e non si finisce mai con comportamenti inaspettati:

TLongLongHashMap map = new TLongLongHashMap();
map.put(1L, 45L);
map.containsKey(1); // returns true, 1 gets promoted to long from int by compiler
int x = map.get(1); // Helpful compiler error. x is not a long
int x = (int)map.get(1); // OK. cast reassures compiler that you know
long x = map.get(1); // Better.

e così via. Non è necessario ottenere il tipo giusto e il compilatore ti dà un errore (che puoi correggere o sovrascrivere) se fai qualcosa di stupido (prova a memorizzare un long in un int).

Le regole del cast automatico fanno sì che anche i confronti funzionino correttamente:

if(map.get(1) == 45) // 1 promoted to long, 45 promoted to long...all is well

Come bonus, l'overhead di memoria e le prestazioni di runtime sono molto migliori.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top