HashMap get/put

Discussion in 'Java' started by Wojtek, Oct 28, 2009.

  1. Wojtek

    Wojtek Guest

    If I have the following:

    HashMap<MyKey,MyValue> map = new HashMap<MyKey,MyValue>();
    MyKey myKey = new MyKey();

    Then I can put in a value by:

    map.put(myKey, new MyValue() );

    The compiler enforces the use of these two types.

    However to do a get I can do the following and the compiler does not
    complain:

    map.get(myKey); // this is right
    map.get(myKey.toString()); // this is wrong yet legal
    map.get(new Long(20)); // this is wrong yet legal

    All of these are legal according to the compiler. Why is it that the
    compiler does not enforce type checking on the get()?

    Or rather, why does the spec not say get(K key) instead of get(Object
    o)

    http://java.sun.com/javase/6/docs/api/java/util/HashMap.html#get(java.lang.Object)

    --
    Wojtek :)
     
    Wojtek, Oct 28, 2009
    #1
    1. Advertising

  2. Wojtek

    Stefan Ram Guest

    Wojtek <> writes:
    >All of these are legal according to the compiler. Why is it that the
    >compiler does not enforce type checking on the get()?


    Setting with a wrong type can result in a Map that breaks
    its contract. Therefore, it is forbidden. Getting with a
    wrong type cannot do such harm, so, it is not forbidden.
    After all, you can meaningfully and correctly detect at run
    time that for a wrong key, there is no value in the map.
     
    Stefan Ram, Oct 28, 2009
    #2
    1. Advertising

  3. Wojtek

    Ken Guest

    On Oct 28, 4:11 pm, -berlin.de (Stefan Ram) wrote:
    > Wojtek <> writes:
    > >All of these are legal according to the compiler. Why is it that the
    > >compiler does not enforce type checking on the get()?

    >
    >   Setting with a wrong type can result in a Map that breaks
    >   its contract. Therefore, it is forbidden. Getting with a
    >   wrong type cannot do such harm, so, it is not forbidden.
    >   After all, you can meaningfully and correctly detect at run
    >   time that for a wrong key, there is no value in the map.


    I don't understand. If the type is not the same as the type used in
    the map there is no chance of success. Clearly this is an error. I'm
    of the mind of the OP.
     
    Ken, Oct 28, 2009
    #3
  4. Wojtek

    Stefan Ram Guest

    Ken <> writes:
    >I don't understand. If the type is not the same as the type used in
    >the map there is no chance of success.


    Look at the following example:

    public class Main
    { public static void main( final java.lang.String[] args )
    {
    final java.util.Map<java.lang.String,java.lang.String> value0
    = new java.util.HashMap<java.lang.String,java.lang.String>();

    final java.util.Map<java.lang.Object,java.lang.Integer> value1
    = new java.util.HashMap<java.lang.Object,java.lang.Integer>();

    final java.util.Map
    <java.util.Map<java.lang.String,java.lang.String>,java.lang.String> map
    = new java.util.HashMap
    <java.util.Map<java.lang.String,java.lang.String>,java.lang.String>();

    map.put( value0, "alpha" );

    java.lang.System.out.println( map.get( value1 )); }}

    alpha

    »value1« has not the type used in the map, yet the »get« succeeds.
     
    Stefan Ram, Oct 28, 2009
    #4
  5. Wojtek

    Wojtek Guest

    Stefan Ram wrote :
    > Wojtek <> writes:
    >> All of these are legal according to the compiler. Why is it that the
    >> compiler does not enforce type checking on the get()?

    >
    > Setting with a wrong type can result in a Map that breaks
    > its contract. Therefore, it is forbidden. Getting with a
    > wrong type cannot do such harm, so, it is not forbidden.
    > After all, you can meaningfully and correctly detect at run
    > time that for a wrong key, there is no value in the map.


    No it is not an error. Just will never retrieve ANY value if you use
    the wrong type (see below).

    This came about because I changed the key for a HashMap to one that is
    more specific (such as String to MyKey). All the "put" statements were
    caught. However ALL the get statements were not. I had to manually go
    through the code to locate each occurrence and change it.

    And of course I missed some and the code broke during runtime. I had
    assumed that generics would solve this type of issue.


    Caveat: Yes it is possible to get the same hashcode with two different
    types, however it would be highly unlikely.

    --
    Wojtek :)
     
    Wojtek, Oct 28, 2009
    #5
  6. Wojtek

    Stefan Ram Guest

    -berlin.de (Stefan Ram) writes:
    >Look at the following example: (...)
    >final java.util.Map<java.lang.String,java.lang.String> value0
    >= new java.util.HashMap<java.lang.String,java.lang.String>(); (...)
    >»value1« has not the type used in the map, yet the »get« succeeds.


    Above, both types were the same after type erasure and were
    types of empty containers.

    Here is a program with types that differ even after type
    erasure and non-empty containers.

    public class Main
    { public static void main( final java.lang.String[] args )
    {
    final java.util.ArrayList< java.lang.String >list0
    = new java.util.ArrayList< java.lang.String >();
    list0.add( "text" );

    final java.util.LinkedList< java.lang.String >list1
    = new java.util.LinkedList< java.lang.String >();
    list1.add( "text" );

    final java.util.Map
    < java.util.ArrayList< java.lang.String >, java.lang.String >map
    = new java.util.HashMap
    < java.util.ArrayList< java.lang.String >, java.lang.String >();

    map.put( list0, "value" );

    java.lang.System.out.println( map.get( list1 )); }}

    value
     
    Stefan Ram, Oct 28, 2009
    #6
  7. On 10/28/2009 05:59 PM, Wojtek wrote:
    > Or rather, why does the spec not say get(K key) instead of get(Object o)


    I can think of some cases where it might be desirable to use gets with
    other objects; this requires, of course, that the objects be designed
    with this use case in mind, namely that the hash codes and equals work
    correctly.

    One example that came up in some code was where I had a parameter
    accessible in two different fashions, and the conversion between the two
    forms became a hotspot in the code.

    --
    Beware of bugs in the above code; I have only proved it correct, not
    tried it. -- Donald E. Knuth
     
    Joshua Cranmer, Oct 28, 2009
    #7
  8. Wojtek

    Lew Guest

    Stefan Ram wrote :
    >> Setting with a wrong type can result in a Map that breaks
    >> its contract. Therefore, it is forbidden. Getting with a
    >> wrong type cannot do such harm, so, it is not forbidden.
    >> After all, you can meaningfully and correctly detect at run
    >> time that for a wrong key, there is no value in the map.


    Wojtek wrote:
    > No it is not an error. Just will never retrieve ANY value if you use the
    > wrong type (see below).
    >
    > This came about because I changed the key for a HashMap to one that is
    > more specific (such as String to MyKey). All the "put" statements were
    > caught. However ALL the get statements were not. I had to manually go
    > through the code to locate each occurrence and change it.
    >
    > And of course I missed some and the code broke during runtime. I had
    > assumed that generics would solve this type of issue.


    Generics only handle what is declared generically. 'Map#get()' is not so
    declared. Assumptions based on falsehood will yield false conclusions.

    <http://java.sun.com/javase/6/docs/api/java/util/Map.html#get(java.lang.Object)>

    > Caveat: Yes it is possible to get the same hashcode with two different
    > types, however it would be highly unlikely.


    And this is relevant how? 'Map' retrieval is defined in terms of 'equals()',
    not 'hashCode()'.

    <http://java.sun.com/javase/6/docs/api/java/util/Map.html#get(java.lang.Object)>
    > ... formally, if this map contains a mapping from a key k to a value v
    > such that (key==null ? k==null : key.equals(k)),
    > then this method returns v; otherwise it returns null.
    > (There can be at most one such mapping.)


    FWIW, if 'Map#get()' had been redefined generically, it would have broken
    existing code without providing an improvement. Code that would have not
    gotten a value still does not get a value without introducing a new error.
    Maybe they erred in this, but they were trying to maintain backward compatibility.

    It's a shame that the compiler didn't find your refactoring mistake for you,
    but it was your mistake, not Java's.

    --
    Lew
     
    Lew, Oct 29, 2009
    #8
  9. In article <>, Wojtek <>
    wrote:

    > If I have the following:
    >
    > HashMap<MyKey,MyValue> map = new HashMap<MyKey,MyValue>();
    > MyKey myKey = new MyKey();
    >
    > Then I can put in a value by:
    >
    > map.put(myKey, new MyValue() );
    >
    > The compiler enforces the use of these two types.
    >
    > However to do a get I can do the following and the compiler does not
    > complain:
    >
    > map.get(myKey); // this is right
    > map.get(myKey.toString()); // this is wrong yet legal
    > map.get(new Long(20)); // this is wrong yet legal
    >
    > All of these are legal according to the compiler. Why is it that the
    > compiler does not enforce type checking on the get()?
    >
    > Or rather, why does the spec not say get(K key) instead of get(Object
    > o)
    >
    > http://java.sun.com/javase/6/docs/api/java/util/HashMap.html#get(java.lang.Obj
    > ect)


    HashMap.java: public V get(Object key)

    There's no generics declared on the key for reading.

    Generics is extremely helpful for common source code mistakes but it is
    by no means a powerful tool. There have been many, many long threads
    about it here.
    --
    I won't see Goolge Groups replies because I must filter them as spam
     
    Kevin McMurtrie, Oct 29, 2009
    #9
  10. Wojtek

    Lew Guest

    Kevin McMurtrie wrote:
    > Generics is extremely helpful for common source code mistakes but it is
    > by no means a powerful tool. There have been many, many long threads
    > about it here.


    There may have been long threads about it, but generics most assuredly is a
    powerful tool. Perhaps you just haven't learned to use it correctly yet.

    --
    Lew
     
    Lew, Oct 29, 2009
    #10
  11. Wojtek wrote:
    > If I have the following:
    >
    > HashMap<MyKey,MyValue> map = new HashMap<MyKey,MyValue>();
    > MyKey myKey = new MyKey();
    >
    > Then I can put in a value by:
    >
    > map.put(myKey, new MyValue() );
    >
    > The compiler enforces the use of these two types.
    >
    > However to do a get I can do the following and the compiler does not
    > complain:
    >
    > map.get(myKey); // this is right
    > map.get(myKey.toString()); // this is wrong yet legal
    > map.get(new Long(20)); // this is wrong yet legal
    >
    > All of these are legal according to the compiler. Why is it that the
    > compiler does not enforce type checking on the get()?
    >
    > Or rather, why does the spec not say get(K key) instead of
    > get(Object
    > o)
    >
    > http://java.sun.com/javase/6/docs/api/java/util/HashMap.html#get(java.lang.Object)


    I can picture (vaguely) looking something up with an object that's
    equal to the original key but not of the same type. This makes at
    least some sense with HashMaps, since it's possible, for objects T1 a
    and T2 b, where T1 and T2 are any types at all, that a.equals(b) and
    a.hashCode() == b.hashCode().

    TreeMap is weirder:

    Map<Integer, String> m = new TreeMap<Integer,String>();
    m.put(2, "foo");
    String val = m.get("bar");

    This compiles just fine, but results in

    Exception in thread "main" java.lang.ClassCastException:
    java.lang.Integer
    at java.lang.String.compareTo(String.java:90)
    at java.util.TreeMap.compare(TreeMap.java:1093)
    at java.util.TreeMap.getEntry(TreeMap.java:347)
    at java.util.TreeMap.get(TreeMap.java:265)
    at WeirdMap.main(WeirdMap.java:11)

    Since a String can't even be compared to the Integer keys.

    Actually, it strikes me as odd that String.compare() is called rather
    than Integer.compare(). I suppose that if you want precise control
    over how comparisons are done, you need to pass in your own
    Comparator.
     
    Mike Schilling, Oct 29, 2009
    #11
  12. Wojtek

    Lew Guest

    Mike Schilling wrote:
    > I can picture (vaguely) looking something up with an object that's
    > equal to the original key but not of the same type. This makes at
    > least some sense with HashMaps, since it's possible, for objects T1 a
    > and T2 b, where T1 and T2 are any types at all, that a.equals(b) and
    > a.hashCode() == b.hashCode().


    I don't think so, not in general nor commonly. Someone who codes disparate
    types that way should be slapped, then fired.

    Remember, to work properly 'equals()' must be reflexive, symmetric and
    transitive. However, you are right that it's possible to violate that.
    Possible, but stupid.

    The common idiom for 'equals()' checks that the argument is of the same type
    as the invoker.

    --
    Lew
     
    Lew, Oct 29, 2009
    #12
  13. Wojtek

    Wojtek Guest

    Lew wrote :
    > And this is relevant how? 'Map' retrieval is defined in terms of 'equals()',
    > not 'hashCode()'.


    The initial lookup up is done with a hashcode.

    You could contrive a case where the hashcode is randomly generated for
    a given object. It will never be found in the HashMap, yet the equals
    would return true.

    --
    Wojtek :)
     
    Wojtek, Oct 29, 2009
    #13
  14. Wojtek

    Wojtek Guest

    Lew wrote :
    > FWIW, if 'Map#get()' had been redefined generically, it would have broken
    > existing code without providing an improvement. Code that would have not
    > gotten a value still does not get a value without introducing a new error.
    > Maybe they erred in this, but they were trying to maintain backward
    > compatibility.


    All sorts of code broke when generics were introduced. Every use of
    each class which had generics needed to be changed. Also changing get()
    would just have been done at that time.

    > It's a shame that the compiler didn't find your refactoring mistake for you,
    > but it was your mistake, not Java's.


    Of course it was my mistake. That is what I am saying. And that is why
    we have compilers which report mistakes, so that they may be caught
    BEFORE they show up during runtime and "weird things" happen.

    --
    Wojtek :)
     
    Wojtek, Oct 29, 2009
    #14
  15. Wojtek

    Wojtek Guest

    Stefan Ram wrote :
    > -berlin.de (Stefan Ram) writes:
    >> Look at the following example: (...)
    >> final java.util.Map<java.lang.String,java.lang.String> value0
    >> = new java.util.HashMap<java.lang.String,java.lang.String>(); (...)
    >> »value1« has not the type used in the map, yet the »get« succeeds.

    >
    > Above, both types were the same after type erasure and were
    > types of empty containers.
    >
    > Here is a program with types that differ even after type
    > erasure and non-empty containers.
    >

    Um, I did a bit of editing in your example, removing the packages. For
    some reason this makes it easier for me to read...

    public class Main
    { public static void main( final String[] args )
    {
    final ArrayList<String> list0 = new ArrayList<String >();
    list0.add( "text" );

    final LinkedList<String > list1 = new LinkedList<String >();
    list1.add( "text" );

    final Map<ArrayList<String>,String> map = new
    HashMap<ArrayList<String>,String >();

    map.put( list0, "value" );

    System.out.println( map.get( list1 ));
    }
    }

    I do not see why this works or why this should work. Maybe because
    "text" was added to both list0 and list1?

    --
    Wojtek :)
     
    Wojtek, Oct 29, 2009
    #15
  16. Wojtek

    Lew Guest

    Wojtek wrote:
    > Um, I did a bit of editing in your example, removing the packages. For
    > some reason this makes it easier for me to read...


    Amen to that!

    Who puts FQNs into java.lang classes?

    Stefan, your examples would be much, much easier to read if you'd
    follow the coding conventions and also use simple type names where the
    context is clear and for java.lang classes. Your lack of
    conventionality turned that example into garbage.

    --
    Lew
     
    Lew, Oct 29, 2009
    #16
  17. Wojtek

    Dave Searles Guest

    Patricia Shanahan wrote:
    > Wojtek wrote:
    >> Lew wrote :
    >>> And this is relevant how? 'Map' retrieval is defined in terms of
    >>> 'equals()', not 'hashCode()'.

    >>
    >> The initial lookup up is done with a hashcode.
    >>
    >> You could contrive a case where the hashcode is randomly generated for
    >> a given object. It will never be found in the HashMap, yet the equals
    >> would return true.

    >
    > You could not have a random hashcode that conforms to the Object
    > hashCode contract, because of the requirement that two objects that are
    > equal according to their equals methods have the same hashCode result.


    Actually, you could if the equals method always returned false. Then
    hashCode conforms. But equals may not, if the equals contract requires
    that if a == b then a.equals(b). In that case, a random-but-set-at-birth
    hashCode and equals equivalent to == is as close as you can get while
    conforming to BOTH cotnracts (and java.lang.Object basically is set up
    like that).
     
    Dave Searles, Oct 29, 2009
    #17
  18. Wojtek

    Lew Guest

    Mike Schilling wrote:
    >>> I can picture (vaguely) looking something up with an object that's
    >>> equal to the original key but not of the same type.  This makes at
    >>> least some sense with HashMaps, since it's possible, for objects T1 a
    >>> and  T2 b, where T1 and T2 are any types at all, that a.equals(b) and
    >>> a.hashCode() == b.hashCode().

    >


    Lew wrote:
    >> I don't think so, not in general nor commonly.  Someone who codes
    >> disparate types that way should be slapped, then fired.

    >


    Patricia Shanahan wrote:
    > That is correct if "disparate" is read as meaning distinct types that do
    > are not sufficiently related that objects of the two types might be equal..
    >


    Yes, that's what I meant. Your example of different implementations
    for 'List' would not be "disparate" in this sense.

    Lew:
    >> Remember, to work properly 'equals()' must be reflexive, symmetric and
    >> transitive.  However, you are right that it's possible to violate that..
    >> Possible, but stupid.

    >


    Patricia:
    > It is possible to have equals methods that allow equality to an object
    > of a different type, not a subclass or superclass, that is still
    > reflexive, symmetric, and transitive.
    >
    > Consider, for example, ArrayList and Stack. They both conform to the
    > java.util.List interface, including its documentation for equals:
    > "Returns true if and only if the specified object is also a list, both
    > lists have the same size, and all corresponding pairs of elements in the
    > two lists are equal."
    >


    These types are not completely disparate, but even to the degree to
    which they are they adhere to the key requirements as you point out.
    They also provide a strong argument for 'Map#get(Object)' instead of
    'Map#get(E)'.

    > The really, really, basic rule about equals is 'Indicates whether some
    > other object is "equal to" this one.'. One would need a breach of that
    > rule to get a false match from HashMap's get.
    >
    > The decision to leave get accepting Object means that one can still
    > probe a Map<Stack, String> with an ArrayList, and get a match if, only
    > if, the Map contains a key that has equal elements in the same order as
    > the ArrayList.
    >


    Lew:
    >> The common idiom for 'equals()' checks that the argument is of the
    >> same type as the invoker.

    >


    Patricia:
    > The more general version of this rule is to test for types of objects
    > that might be logically equal to this one. A List equals() method should
    > test whether its argument is a List, not whether it is the same type as
    > the specific List implementation.


    Excellent points that explain why Java might not have wanted to
    genericize 'Map#get()'. They might've done better with some sort of
    bounded wildcard argument, but 'Object' works just fine.

    The key insight from your example is that for the purpose of
    comparison, 'List' implementations are viewed at the level of the
    interface type, and not at the level of the concrete type. At that
    level, 'ArrayList' and 'Stack' are not disparate.

    --
    Lew
     
    Lew, Oct 29, 2009
    #18
  19. Wojtek

    Lew Guest

    Wojtek wrote:
    > Lew wrote :
    >> FWIW, if 'Map#get()' had been redefined generically, it would have
    >> broken existing code without providing an improvement. Code that
    >> would have not gotten a value still does not get a value without
    >> introducing a new error. Maybe they erred in this, but they were
    >> trying to maintain backward compatibility.

    >
    > All sorts of code broke when generics were introduced. Every use of each
    > class which had generics needed to be changed. Also changing get() would
    > just have been done at that time.
    >
    >> It's a shame that the compiler didn't find your refactoring mistake
    >> for you, but it was your mistake, not Java's.

    >
    > Of course it was my mistake. That is what I am saying. And that is why
    > we have compilers which report mistakes, so that they may be caught
    > BEFORE they show up during runtime and "weird things" happen.


    But you cannot expect the compiler to catch all your mistakes. So this was
    one that it couldn't catch because in their infinite wisdom the Powers That Be
    decreed that 'Map#get()' not be type parameterized. A few minutes with the
    'Map' Javadocs before you refactored would've saved you some trouble. Then
    you could've used Eclipse's Ctrl-Shift-G (find all references) or the
    equivalent for your IDE and fixed the problem with even less effort than
    letting the compiler find the error would have caused.

    I think we're spoiled by how much help Java gives us to catch and prevent
    error. We start whining like little brats when we reach one of those corners
    that we have to sweep with our own effort.

    You did the community a good service by alerting us to this particular corner.

    --
    Lew
     
    Lew, Oct 30, 2009
    #19
  20. > But you cannot expect the compiler to catch all your mistakes. So
    > this was one that it couldn't catch because in their infinite wisdom
    > the Powers That Be decreed that 'Map#get()' not be type
    > parameterized.


    Which we still don't have a good explanation for. The closest to a
    real use case for get(Object) rather than get(K) we've seen is
    Patricia's, and that could be made to work with get(K) simply enough
    by letting K be List instead of ArrayList.
     
    Mike Schilling, Oct 30, 2009
    #20
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Vince Darley
    Replies:
    4
    Views:
    4,447
    emilchacko
    Mar 2, 2010
  2. Red Orchid
    Replies:
    3
    Views:
    1,054
  3. Composer

    HashMap.put error in Xcode

    Composer, Apr 22, 2007, in forum: Java
    Replies:
    9
    Views:
    1,185
    Daniel Pitts
    Apr 23, 2007
  4. Rakesh
    Replies:
    10
    Views:
    12,188
    Mike Schilling
    Apr 8, 2008
  5. Gabriel Rossetti
    Replies:
    3
    Views:
    564
    Jerry Hill
    Apr 25, 2008
Loading...

Share This Page