Wieso HashMap-Zugriff mit Object, statt mit MyObject?

Status
Nicht offen für weitere Antworten.

Professor Chaos

Aktives Mitglied
Hi,

ich habe eine rein technische Verständnisfrage.
Es gibt doch nun seit einiger Zeit (seit Java 6?) die Parametrisierung (oder wie man das sonst nennen mag), also Typensicherheit. Sprich, man gibt Datenstrukturen nun an, was man hineinspeichert.

Zum Beispiel hier:
Code:
HashMap<Integer,String> mappingInt2String = new HashMap<Integer,String>();

Ich verstehe nicht, wieso der Eclipse Syntaxchecker es zulässt, dass man in die get()-Methode beliebige Onjekte reinsteckt.
Eine smart-ass-Antwort würde wohl lauten: Weil der Prototyp der get()-Methode eben Object als Argument hat.
Doch ich frage nach dem "Wieso"!

Schließlich ist Eclipse doch auch intelligent genug, zu erkennen, was ich reinstecken darf. D.h. die Zeile
Code:
mappingInt2String.put(new Integer(42), new Integer(23));
wird korrekter Weise als Fehler angezeigt: "Ich solle doch bitte (Integer,String) reinstecken, und nicht (Integer,Integer)".

Wieso meckert Eclipse also nicht bei folgender Zeile?
Code:
mappingInt2String.get("strange...");

Bzw., um Spitzfindigkeiten auszuschließen: wieso beinhaltet der Prototyp von get() den Argumenttypen "Object", statt den "von mir" angegebenen Typen (in diesem Fall "Integer")?


--------
Falls dieses Thema von einem Admin gelesen werden sollte:
Könnte dieser den Titel nach "Wieso HashMap-Zugriff mit Object, statt mit MyObject?" ändern?
Ich erhalte (wie auch andere) die Meldung "Der Titel darf aus Spamschutzgründen nicht XXX sein!"
 
Erstmal grundsätzliches: Eclipse oder andere IDEs haben RECHT hier nicht zu meckern es ist so implementiert.

Wieso SUN das so implementiert hat weiß ich nicht, wie Gast schon sagte, vielleicht aus Gründen der Abwärtskompatibilität.

Die Implementation der GET-Methode in java.util.Map ist aber so:

Code:
    /**
     * Returns the value to which the specified key is mapped,
     * or {@code null} if this map contains no mapping for the key.
     *
     * 

More formally, if this map contains a mapping from a key
     * {@code k} to a value {@code v} such that {@code (key==null ? k==null :
     * key.equals(k))}, then this method returns {@code v}; otherwise
     * it returns {@code null}.  (There can be at most one such mapping.)
     *
     * 

If this map permits null values, then a return value of
     * {@code null} does not [i]necessarily[/i] indicate that the map
     * contains no mapping for the key; it's also possible that the map
     * explicitly maps the key to {@code null}.  The {@link #containsKey
     * containsKey} operation may be used to distinguish these two cases.
     *
     * @param key the key whose associated value is to be returned
     * @return the value to which the specified key is mapped, or
     *         {@code null} if this map contains no mapping for the key
     * @throws ClassCastException if the key is of an inappropriate type for
     *         this map (optional)
     * @throws NullPointerException if the specified key is null and this map
     *         does not permit null keys (optional)
     */
    V get(Object key);

während für die PUT-Methode die "passente" Implementation (mit Generics) gewählt wurde:

Code:
    /**
     * Copies all of the mappings from the specified map to this map
     * (optional operation).  The effect of this call is equivalent to that
     * of calling {@link #put(Object,Object) put(k, v)} on this map once
     * for each mapping from key <tt>k</tt> to value <tt>v</tt> in the
     * specified map.  The behavior of this operation is undefined if the
     * specified map is modified while the operation is in progress.
     *
     * @param m mappings to be stored in this map
     * @throws UnsupportedOperationException if the <tt>putAll</tt> operation
     *         is not supported by this map
     * @throws ClassCastException if the class of a key or value in the
     *         specified map prevents it from being stored in this map
     * @throws NullPointerException if the specified map is null, or if
     *         this map does not permit null keys or values, and the
     *         specified map contains null keys or values
     * @throws IllegalArgumentException if some property of a key or value in
     *         the specified map prevents it from being stored in this map
     */
    void putAll(Map<? extends K, ? extends V> m);

Wie gesagt wieso das so implementiert ist weiss ich auch nicht aber Eclipse liegt hier auf jeden Fall richtig.

Gruß
Eminent
 
Ich vermute mal, der Grund liegt - wie so vieles bei Generics - am Type Erasure.

Wenn man der get-Methode ein Object einer falschen Klasse übergibt, passiert erstmal nichts falsches, es kann dabei zu keiner ClassCastException kommen, da das Objekt nicht in der Map gespeichert wird.

Interessant wird der Fall, wenn man Wildcards arbeitet. Ich hole jetzt mal was aus.

Code:
List <? extends Number> myList;

Hier kann man die add-Methode nicht aufrufen, da der genaue Typ der Liste unbekannt ist.
Es könnte eine List<Number> sein oder eine List<Long> oder eine List<Float>. Und es gibt keinen Typ, der zu allen 3 Klassen (Number, Long, Float) passt.

Dafür liefert die get-Methode ein Number-Objekt, da klar ist, dass mindestens Numbers in der Liste stecken.

Nun der umgekehrte Fall:
Code:
List <? super Number> myList;

Hier kann man die Add-Methode mit Number aufrufen, da man weiß, dass der Typ, den die Liste hat eine Oberklasse von Number ist, also auf jeden Fall Number darauf gecastet werden kann.

Dafür liefert die get-Methode ein Objekt vom Typ Object, da man nicht weiß welcher Obertyp in der Liste steckt.

Zusammenfassend:
Bei Wildcards extends sind Methoden mit generischen Parametern nicht aufrufbar, Methoden mit generischen Rückgabeparametern liefern den entsprechenden Typ.
Bei Wildcards super sind Methoden mit generischen Parametern aufrufbar, Methoden mit generischen Rückgabeparametern liefern Object.

Und nun wieder zurück zur Map:
Würde die get-Methode einen generischen Typ haben, so könnte sie, sobald man für den Key die Wildcard mit extends nimmt, nicht mehr aufgerufen werden. Nimmt man die Wildcard mit super, liefert sie Object zurück. Beides unschön.
Durch die Wahl von Object als Parameter kann man die Methode bei Wildcards mit extends aufrufen und bekommt den entsprechend schönen Rückgabewert.

Ob das der Grund für die Entscheidung war, weiß ich nicht, kann es mir jedoch gut vorstellen.
 
Ich hatte mir (und dem Forum :wink: ) die gleiche Frage auch mal gestellt http://www.java-forum.org/de/viewtopic.php?t=52402 aber keine wirklich überzeugende, abschließende Antwort erhalten... Das Web liefert Antworten von "Ist doch egal" bis "Abwärtskompatibilität" bis "Funktioniert nicht". Hm. Ich denke inzwischen, dass es einige ... "oddities" erzeugen würde.
Wie auch immer. Laut http://java.sun.com/j2se/1.5.0/docs/api/java/util/Map.html#get(java.lang.Object) kann die Methode "optional" eine ClassCastException werfen, wenn der falsche Typ übergeben wird.
 
Marco13 hat gesagt.:
Laut http://java.sun.com/j2se/1.5.0/docs/api/java/util/Map.html#get(java.lang.Object) kann die Methode "optional" eine ClassCastException werfen, wenn der falsche Typ übergeben wird.
Tatsächlich, siehe:
Java API hat gesagt.:
V get(Object key)[...]
Throws:
ClassCastException - if the value is of an inappropriate type for this map (optional).
Aber wie soll das funktionieren? Denn ich möchte ja gerade, dass Eclipse einen Fehler (keine Warnung) wirft, wenn ich mit einem key zugreife, der einen anderen Typ hat, als angedacht. Ich möchte also genau diese optionale Exception. Aber wie teile ich mit, dass diese auch geworfen werden soll? Die API schweigt sich dazu aus.
 
Lim_Dul hat gesagt.:
Das teilst du nicht mit, das "entscheidet" die entsprechende Implementierung der Map
Ach, natürlich. Ich habe nicht gesehen, dass ich mich in der Documentation des Interfaces befand. Ich dachte, das wäre die Documentation der konkreten Klasse HashMap.
Schade.
Gut, man könnte natürlich eine eigene Klasse MyHashMap anlegen, die gänzlich identisch ist und lediglich get() überschreibt. Aber ist das schön? Ich passe wohl besser auf, was ich tue, statt eigene Klassen einzuführen.
 
einer der Autoren (J.Bloch) der Collection API spricht darüber.

Well, It's typesafe..
..in order to maximize backward compatibility...
In fact, when we were generifying the collection....we tried to attempt this restriction but we find out it simply didn't work

weils generisch nicht so gut geht gewisse Dinge zu implementieren
Abwärtskompatibilität...

auch remove und contains ist so...
man kann JFrame von Set<String> entfernen..bekommt
einfach null zurück...dito get bei Map...

so kurz gesagt...
 
Status
Nicht offen für weitere Antworten.

Zurück
Oben