• Post Reply Bookmark Topic Watch Topic
  • New Topic
programming forums Java Mobile Certification Databases Caching Books Engineering Micro Controllers OS Languages Paradigms IDEs Build Tools Frameworks Application Servers Open Source This Site Careers Other Pie Elite all forums
this forum made possible by our volunteer staff, including ...
Marshals:
  • Campbell Ritchie
  • Ron McLeod
  • Rob Spoor
  • Tim Cooke
  • Junilu Lacar
Sheriffs:
  • Henry Wong
  • Liutauras Vilda
  • Jeanne Boyarsky
Saloon Keepers:
  • Jesse Silverman
  • Tim Holloway
  • Stephan van Hulst
  • Tim Moores
  • Carey Brown
Bartenders:
  • Al Hobbs
  • Mikalai Zaikin
  • Piet Souris

HashMap doesn't show/contain the keys!

 
Greenhorn
Posts: 5
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Hi,

In the following code and in the function helper, I put some (key, value) of type (LinkedList<Integer>, Integer) in a HashMap. The process seems to be OK. However, when I check for the keySet of the HashMap in the dfs function, where I call the helper for the first time, it just contains empty linkedLists.

Any ideas on the problem?






 
Sheriff
Posts: 26791
82
Eclipse IDE Firefox Browser MySQL Database
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
My idea would be to find the place(s) where you are adding entries to the Map and see why the LinkedList you're using as a key is empty. For me that would involve digging through a large quantity of code, but for you it would be easier because you (I presume) already know your way around it. If you want to point to some particular location in the code where that happens, I'm sure people would be able to look there and investigate.
 
Master Rancher
Posts: 4032
54
  • Likes 3
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
It looks like the place you're adding keys is here:

From this I can see that this "alphaList" is something you're adding and removing values from, and you're putting it in as key.  This is a major problem.  If you put a mutable object in a hashmap as a key, and you change the data in the mutable object afterwards, you confuse the heck out of the hashmap.  Furthermore, if that code is run 100 times, your hashmap may look like it has 100 entries with 100 keys - but they aren't 100 different keys (even though they were supposed to be".  They're 100 different references to the same single LinkedList object, which you keep changing as you go. That's no good.  You need to create a new key object for each entry into the map.  And don't change it afterwards.

One simple way to do this would be to call the LinkedList's clone() method.  This makes a copy of the list with the same contents it had at the time you cloned it.  If you want to keep using that alphaList, ok, but never put the alphaList itself into the map.  Always put a clone() of the list into the map, and never change any data in the cloned key.
 
Saloon Keeper
Posts: 1327
40
Eclipse IDE Postgres Database C++ Java
  • Likes 1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
You have some very long physical lines making the code harder to read.

One of those is:


Which, if you are using Java 7 or newer, could be written:
 
elnaz mehrzadeh
Greenhorn
Posts: 5
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Mike Simmons wrote:It looks like the place you're adding keys is here:

From this I can see that this "alphaList" is something you're adding and removing values from, and you're putting it in as key.  This is a major problem.  If you put a mutable object in a hashmap as a key, and you change the data in the mutable object afterwards, you confuse the heck out of the hashmap.  Furthermore, if that code is run 100 times, your hashmap may look like it has 100 entries with 100 keys - but they aren't 100 different keys (even though they were supposed to be".  They're 100 different references to the same single LinkedList object, which you keep changing as you go. That's no good.  You need to create a new key object for each entry into the map.  And don't change it afterwards.

One simple way to do this would be to call the LinkedList's clone() method.  This makes a copy of the list with the same contents it had at the time you cloned it.  If you want to keep using that alphaList, ok, but never put the alphaList itself into the map.  Always put a clone() of the list into the map, and never change any data in the cloned key.




Ooooh, now I get it.
Thanks for the thorough explanation, Mike.
 
Marshal
Posts: 22457
121
Eclipse IDE Spring VI Editor Chrome Java Windows
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Mike Simmons wrote:One simple way to do this would be to call the LinkedList's clone() method.  This makes a copy of the list with the same contents it had at the time you cloned it.  If you want to keep using that alphaList, ok, but never put the alphaList itself into the map.  Always put a clone() of the list into the map, and never change any data in the cloned key.


If you're using Java 9 of higher, use List.copyOf instead. It will return not just a copy, but more importantly an immutable copy. Well, as far as the list itself goes. Its contents are still mutable, but it prevents accidentally modifying the lists.

Mike's previous comments about mutable objects as map keys (and similarly, as set element) is very important. If you modify a map key or set element, you mess up the entire way that maps and sets work. It's because the internal location of the key / element needs to change but doesn't. As a result, the map / set looks in the wrong place.

A simple example:
 
Marshal
Posts: 74054
332
  • Likes 1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Rob Spoor wrote:. . . List.copyOf instead. It will return not just a copy, but more importantly an immutable copy. . . . . Its contents are still mutable . . .

I think OP was using a List<Integer>, so that problem won't arise; Integer is itself immutable.
You can get similar problems with sets if you overload equals() rather than overriding it. ...equals(Foo f)...=bad. ...equals(Object ob)...=correct.
 
Rob Spoor
Marshal
Posts: 22457
121
Eclipse IDE Spring VI Editor Chrome Java Windows
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
You're right. I saw some List<int[][]>, and these arrays of arrays are mutable. The map keys are indeed List<Integer>, and then the result of List.copyOf will be fully immutable and therefore valid for map keys.
 
Mike Simmons
Master Rancher
Posts: 4032
54
  • Likes 1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Rob Spoor wrote:You're right. I saw some List<int[][]>, and these arrays of arrays are mutable. The map keys are indeed List<Integer>, and then the result of List.copyOf will be fully immutable and therefore valid for map keys.


Going back to List<int[][]>, if those had been used as keys, not only would they be mutable, but also arrays don't have useful hashCode() and equals() methods, so they would be very poor keys indeed.  Better to convert to List<List<List<Integer>>> or some such, if you needed to use that data as a key.  Of course, that's not what was happening here, so never mind, but just in case anyone does try to use arrays in a key, please don't!
 
Campbell Ritchie
Marshal
Posts: 74054
332
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
A List<Integer> might possibly be made immutable with copyOf(), and also with older methods like Collections.unmodifiableList()...but making a List<List<Integer>> immutable looks quite a challenge. It can probably be done with techniques similar to the above, I expect.
 
Mike Simmons
Master Rancher
Posts: 4032
54
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Then again, if you make your keys immutable in the first place, you don't need to copy them; you can pass around references to the key freely.  So the problem is not so much how to make an immutable copy (of a list of lists, or whatever), but how to make it immutable in the first place.
 
Campbell Ritchie
Marshal
Posts: 74054
332
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Agree, Mike.
 
Saloon Keeper
Posts: 24325
167
Android Eclipse IDE Tomcat Server Redhat Java Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Without straining my feeble eyes on that eye-watering amount of code, what you are describing sounds like you're attempting to do is dump a whole list of stuff into the Map at one time.

A Map contains a set of key/value pairs. The value can be null, but for a HashMap, the key cannot, and each key must be a distinct object, not "==" any other key object.

If you have a list of key/value pairs, the proper way to add them is to enumerate that list and add them one by one, since an internal pair object must be created to connect them within the Map. If you can find a method that does that enumeration for you, that's great, but just dumping in the whole list directly isn't going to do it.

Also, I recommend that you use braces on your dependent clauses for loops and conditionals. Not doing so is legal and it's popular, but it can also bite you.
 
Mike Simmons
Master Rancher
Posts: 4032
54
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Tim Holloway wrote:A Map contains a set of key/value pairs. The value can be null, but for a HashMap, the key cannot, and each key must be a distinct object, not "==" any other key object.


Confusingly, the key in a java.util.HashMap can be null. (For exactly one entry - if you add another, you overwrite the original entry, just like you would if you duplicated any non-null key.) But for the older java.util.Hashtable, a null key is not allowed.
 
Campbell Ritchie
Marshal
Posts: 74054
332
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Mike Simmons wrote:. . . for the older java.util.Hashtable, a null key is not allowed.

Nor, if I remember correctly, is a null permissible as a value. Hashtable HashMap
 
Rob Spoor
Marshal
Posts: 22457
121
Eclipse IDE Spring VI Editor Chrome Java Windows
  • Likes 1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Correct.

The use of null for a key or value depends on the implementation. For instance:
  • HashMap allows both
  • TreeMap allows null values, but only allows null keys if a custom Comparator is used that supports null arguments
  • ConcurrentHashMap, like Hashtable, supports neither

  • When you need null keys or values, always first look if the implementation you're thinking of supports it.
     
    Jesse Silverman
    Saloon Keeper
    Posts: 1327
    40
    Eclipse IDE Postgres Database C++ Java
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator

    Rob Spoor wrote:Correct.

    The use of null for a key or value depends on the implementation. For instance:

  • HashMap allows both
  • TreeMap allows null values, but only allows null keys if a custom Comparator is used that supports null arguments
  • ConcurrentHashMap, like Hashtable, supports neither

  • When you need null keys or values, always first look if the implementation you're thinking of supports it.



    One more thing in the "High Value Is Null Allowed Or Not Trivia" category, from Javadocs:

    The Map.of, Map.ofEntries, and Map.copyOf static factory methods provide a convenient way to create unmodifiable maps. The Map instances created by these methods have the following characteristics:

    ...
    They disallow null keys and values. Attempts to create them with null keys or values result in NullPointerException.



    People who like to memorize stuff seem to be in love with remembering the rules for where null is and is not allowed with every sort of Map.
    That may or may not be overkill, but remembering to check the docs if it is going to matter to you is very important.
    I remember hearing during a presentation one of the key Collections folks from Sun/Java saying they all agreed ever allowing nulls in as keys or values was a mistake that they could never fix without a time machine, but they avoided allowing them in any time something new was actually added once they realized this.

    Reviewing Map methods, .merge(), .put(), .remove(), .replace() and a couple of others pretty much treat keys corresponding to null values and non-existent keys the same.

    Taken together I feel avoiding null values all the time might be a best practice, so you don't get caught out if you change implementations, etc. and so you don't need to remember all the rules about where they are allowed.  Like many best practices, you should remember (or at least know where to find in one handy place) the rules if you don't follow it.  The part of this post before the commentary may be one such place.
     
    Campbell Ritchie
    Marshal
    Posts: 74054
    332
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator
    Since this thread isn't as dead as I thought it was, in the old days people would writerather than...and come unstuck with a HashMap which permits null as both keys and values. I was pleased to hear what Jesse said about that being a mistake.
     
    Mike Simmons
    Master Rancher
    Posts: 4032
    54
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator
    Well, in the old days I always did that, knowing that I would never actually want to put a null key or value in a map, even if it was allowed.  And if somehow a null value was in the map... how is that different in effect from the key not being in the map?  It's just two different ways of saying we don't have a value for that key. . So I always treated them as meaning the same thing, and never used containsKey if I could help it.
     
    Campbell Ritchie
    Marshal
    Posts: 74054
    332
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator
    You know not to put null into a Map, I know not to put null into a Map, but lots of people will still do that. And come undone if they are expecting that strictly means no mapping. Defensive programming is a bit like defensive driving: you have to assume that there are a few *****s out there. And never assume people will use your API correctly. That is better than getting into a row and telling them, if they'd read the ****** instructions, this would never have happened.
     
    Mike Simmons
    Master Rancher
    Posts: 4032
    54
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator
    Okay, so I just use get() rather than containsKey(), and treat a null as meaning "it's not there"... why does it matter to me whether the key was mapped to null, or the key wasn't there at all?  Other than the former being a little more wasteful.  My position is, the distinction doesn't matter, so why bother with containsKey at all?  I'm not sure how that leads to someone coming undone.

    I would definitely prefer to block nulls from being put in the map in the first place.  But if one gets in accidentally... I still don't see a big problem with just ignoring it and treating it as no key found.
     
    Campbell Ritchie
    Marshal
    Posts: 74054
    332
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator
    ...but the HashMap documentation allows a key to be null or to map to null. Agree it would have been better to ban nulls, as the old implementation did, and as Map#copyOf() and Map#of(...) do. Maybe allowing nulls is a mistake, and I was pleased to hear Jesse say that. Nasty case of violating the DRY principle.
    People might come unstuck when they interpret getting null as meaning there isn't a mapping that hasn't been totally removed. That is what the containSxxx() methods look for. Maybe null is a valid value.
    Maybe somebody used a Map as a phone book and before 1963 it might have contained “Campbell↦277”. [Neither phone number mentioned exists nowadays.]
    From 1963-1966 it might have contained “Campbell↦null”. It will come as a surprise to many readers that in those days there were many people without phone numbers full stop. In those days we didn't havae a phone and the mapping to null recognises that absence. Permitting null keys is one way to implement that and it would be a way to record, “Campbell hasn't got a phone number.” It doesn't mean that getting null implies no record. On paper it might have read, “Campbell 277”.
    In 1966, that mapping would have changed to “Campbell↦64831”. Maybe maps had been invented in 1963. Maybe this is an argument for a value being null, but it would be better to use one of the techniques for avoiding nulls.
    The old implementation prohibited nulls, which meant, rather than recording no value one had to delete the key and the whole Entry. Other implementations permit inactivating a mapping by putting null.
     
    Mike Simmons
    Master Rancher
    Posts: 4032
    54
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator
    I think we're all on the same page that we agree it's better that, in new classes and methods at least, they don't allow null keys or null values.  But for maps that do allow null, as HashMap still does... I understand that in principle, it's possible that we might use this to represent a difference between "we don't know Campbell's phone number" and  "we know that Campbell has no phone".  But in practice, I have never worked on code where such a distinction mattered; I've been happy treating those as equivalent situations.  Either way, we don't have a number, and therefore can't call him.
     
    Campbell Ritchie
    Marshal
    Posts: 74054
    332
    • Likes 1
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator

    Mike Simmons wrote:I think we're all on the same page . . .

    we don't have a number, and therefore can't call him.

    ...Peace and quiet!
     
    Mike Simmons
    Master Rancher
    Posts: 4032
    54
    • Likes 1
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator

     
    Jesse Silverman
    Saloon Keeper
    Posts: 1327
    40
    Eclipse IDE Postgres Database C++ Java
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator

    Campbell Ritchie wrote:...but the HashMap documentation allows a key to be null or to map to null. Agree it would have been better to ban nulls, as the old implementation did, and as Map#copyOf() and Map#of(...) do. Maybe allowing nulls is a mistake, and I was pleased to hear Jesse say that. Nasty case of violating the DRY principle.
    People might come unstuck when they interpret getting null as meaning there isn't a mapping that hasn't been totally removed. That is what the containSxxx() methods look for. Maybe null is a valid value.
    Maybe somebody used a Map as a phone book and before 1963 it might have contained “Campbell↦277”. [Neither phone number mentioned exists nowadays.]
    From 1963-1966 it might have contained “Campbell↦null”. It will come as a surprise to many readers that in those days there were many people without phone numbers full stop. In those days we didn't havae a phone and the mapping to null recognises that absence. Permitting null keys is one way to implement that and it would be a way to record, “Campbell hasn't got a phone number.” It doesn't mean that getting null implies no record. On paper it might have read, “Campbell 277”.
    In 1966, that mapping would have changed to “Campbell↦64831”. Maybe maps had been invented in 1963. Maybe this is an argument for a value being null, but it would be better to use one of the techniques for avoiding nulls.
    The old implementation prohibited nulls, which meant, rather than recording no value one had to delete the key and the whole Entry. Other implementations permit inactivating a mapping by putting null.



    I've spent two million hours reading Chris J. Date and his buddy Hugh Darwen and their friends rant against the horrors of NULL in SQL.
    There are additional problems involving NULL in SQL, in that it is handled inconsistently in different constructs in the language.
    But they also write about the data interpretation problems that come from its overloaded meanings, mostly usually one of these two:
    n/a -- the 3 year-old doesn't have any children, and we are sure of this
    unknown -- the 58 year-old murder victim we found may or may not have kids, we haven't even figured out his name or address yet.

    Then this starts exploding, we know something has a real value in reality, but know that we don't know what it is, etc.

    For a good part of my career, I considered NULL pointers and references to be totally normal and extremely well-defined, while nodding my head along to the diatribes of how terrible they were in database tables and queries.  NULL pointers and references simply meant "Yes, we have no bananas" but the ones found in tables meant...???  who knows ???  The same thing, obviously there is a value but we don't know it, we don't know whether there is a value and we just don't know it or it may not exist...

    Somehow on the SQL side I totally stressed about this stuff, on the coding side I just make sure not to de-reference a null pointer/reference.  I think about data differently when it isn't in a table, it seems.

    But to some extent that was a false dichotomy I was creating.

    Ah yes, the point of all this.  "Learning Perl" for instance totally belabors the night-and--day difference between key doesn't exist, and key exists and maps to null value.
    Now, what is the semantic distinction??  I think that brings us into the philosophical/mathematical/modelling realm that freaks out the Date crew.

    It is interesting to hear Mike's take on "No key or null value?  No difference to me, just means 'not there'"...kind of sidestepping assigning possibly different meanings to them.

    If all of the API methods consistently treat the two cases as the same we could go that way, I vaguely feel there are some lumps in that even using the structures that allow null values.  I can't state what/which they are tho.  And of course, there are many implementations/varieties of Map that will not tolerate them, so programming to the interface/lowest-common-denominator it feels like a good idea to eschew them in Java.
     
    Jesse Silverman
    Saloon Keeper
    Posts: 1327
    40
    Eclipse IDE Postgres Database C++ Java
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator
    One more thing on the same theme.

    I mentioned several API methods on Map that behave the same for missing keys and null values (if they ever get in there).
    Well this one makes sure it never leaves nor puts a null value in for any Map type it is called on, ever...

    default V merge​(K key,
    V value,
    BiFunction<? super V,​? super V,​? extends V> remappingFunction)

    If the specified key is not already associated with a value or is associated with null, associates it with the given non-null value. Otherwise, replaces the associated value with the results of the given remapping function, or removes if the result is null.

    Were you merging with those keys?  'cause not one of the values is null now!
     
    Mike Simmons
    Master Rancher
    Posts: 4032
    54
    • Likes 1
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator
    Yup.  The new methods convert null values into no mapping.  If you considered those to mean different things, they're the same now.  For those of us who didn't care about that distinction in the first place, that works out just fine.  
     
    Jesse Silverman
    Saloon Keeper
    Posts: 1327
    40
    Eclipse IDE Postgres Database C++ Java
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator
    Last thing (I think) because my aversion to null extends beyond Maps of any flavor.

    I see so, so, so, so much code by people that code full time professionally and also teach where they make rampant use of unboxing:
    BinaryOperator<Integer> add = (I1, I2) -> I1 + I2;

    If this is called with two int primitives, we will auto-box, unbox, add, and auto-box, I think.

    If this is called with two non-null reference Integers, it will unbox, add, and auto-box, I think.

    If this is called with one or more null values for an Integer reference, it will blow up like an exploding cigar.

    I'd prefer to write:
    IntBinaryOperator add2 = (i1, i2) -> i1 + i2;

    If this is called with two int primitives, it will do no boxing and no unboxing.
    If this is called with two non-null reference Integers, it will unbox, add and return.

    If this is called with one or more non-null values for an Integer reference, it will blow up, but at the code site that calls it instead of inside the lambda?  That's a little better, I think?

    I don't know if this is a big deal or not.  It seems like one to me, but I am noticing a lot of presentations all but ignore the primitive specializations for the functional interfaces, even if they beat the other stuff to death?

    Nulls in general make me nervous, they feel like the code equivalent of banana peels left on the workplace floor.  It feels like Java has so much unboxing going on around various operators (+ - * / <= >) with little or no attention paid to it that this hazard of life is...even more of a hazard. I think I am sold on Optional, but am still studying it...
     
    Campbell Ritchie
    Marshal
    Posts: 74054
    332
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator

    Jesse Silverman wrote:. . . "Yes, we have no bananas" . . .

    No, it means, “No, we have lots of banana skins.”
    As you said, NULL sometimes means, “no value,” and sometimes means, “don't know,” in SQL.
    You can't phone me here; there isn't even a mobile signal.
     
    Jesse Silverman
    Saloon Keeper
    Posts: 1327
    40
    Eclipse IDE Postgres Database C++ Java
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator
    More reasons to avoid null values even in Map implementations that allow them.

    I was trying to see if at least everything is handled consistently across different API methods.

    That is, everything either considers NULL value to be same as missing key, or everything considers them to be two different things.

    The resultant mess I am seeing makes me think if they could they would have just gone the first route, they refused to do the second because they need to preserve identical legacy behavior, and the only sane way to approach it is to just avoid null values like ants at a picnic.

    The following methods treat NULL value and missing key as different, i.e. a NULL value is a perfectly legitimate value that happens to be null:
    boolean containsKey​(Object key) # still true if associated with NULL value (if applicable for that Map type)
    boolean containsValue​(Object value) # returns true if any key associated with NULL value, false if none happen to be or none can be fore that map type...
    Set<Map.Entry<K,​V>> entrySet() # no special treatment for NULL values...
    default void forEach​(BiConsumer<? super K,​? super V> action) # no special treatment for NULL values...
    V get​(Object key) # Results don't tell you if key was absent or value was NULL, use .containsKey() if you care...
    default V getOrDefault​(Object key, V defaultValue) # NULL value treated just as non-null, returns NULL, not default value when key associated with NULL value
    V remove​(Object key) # same as above...
    V put​(K key, V value) # same as above but may throw NullPointerException  if value is NULL and NULL values forbidden
    void putAll​(Map<? extends K,​? extends V> m) # can throw NullPointerException  if source contains NULLs and destination doesn't permit them, otherwise no special treatment.
    Set<K> keySet() # has very interesting behavior in other ways, but treats NULL values exactly the same as any others...
    Collection<V> values() # also has interesting properties, but treats NULL values exactly the same as any others...
    Set<Map.Entry<K,​V>> entrySet() # same as above
    default void forEach​(BiConsumer<? super K,​? super V> action) # no special treatment for NULL values...
    default void replaceAll​(BiFunction<? super K,​? super V,​? extends V> function) #can throw NullPointerException if replacing with NULLs and destination doesn't permit them, otherwise no special treatment.
    default boolean remove​(Object key, Object value) # returns TRUE if mapped to NULL value, FALSE if key not present, either way, it's not there now.
    default V replace​(K key, V value) # no special treatment for keys mapped to NULL values
    default boolean replace​(K key, V oldValue, V newValue) # if oldValue NULL, only replaces if key was there, else does nothing and returns FALSE

    okay, all classical API methods on Map provide no special treatment for NULL values, they are the same as any other except as noted, sometimes you won't be able to distinguish if a key was absent or mapped to NULL from your return value.  Maybe except remembering where and when they are allowed, and not tripping on them in processing contents, they aren't so special?

    Nope, because the following methods all treat keys mapped to NULL values as the same as not present, rather than a key that just happens to be mapped to NULL:
    default V putIfAbsent​(K key, V value) # note with delight that tho getOrDefault() treats NULL & non-NULL the same, putIfAbsent() overwrites existing NULL value associated with key with supplied value, as if missing key.  If new value supplied is NULL, leaves existing NULL value for key or adds new entry for key with NULL value.  So for existing value, NULL same as missing key, but replacement with NULL is the same as replacing with any other value.  It does NOT remove the entry, and will even add a new entry for key with NULL value if asked to.
    # that definitely feels a bit surprising/asymmetrical, something to be remembered...

    default V computeIfAbsent​(K key, Function<? super K,​? extends V> mappingFunction) keys with NULL values are considered ABSENT (like putIfAbsent).  A Map to NULL leaves entry for key unmodified if it was present and previously mapped to NULL, rather than remvoing it, but will never add an entry for Key mapped to NULL even if mapping function returns NULL...even more to remember!

    default V computeIfPresent​(K key, BiFunction<? super K,​? super V,​? extends V> remappingFunction)
    # If the value for the specified key is present and non-null, attempts to compute a new mapping given the key and its current mapped value.
    # If the remapping function returns null, the mapping is removed.
    # So if key existed and was mapped to NULL, left as it was, because "not Present".  If we attempt to set new value to NULL, the entry will be deleted.

    default V compute​(K key, BiFunction<? super K,​? super V,​? extends V> remappingFunction)
    compute behavior is so complex it is easiest to just say it does this:


    but in fairness they do say it is often easier to use merge, so...

    default V merge​(K key, V value, BiFunction<? super V,​? super V,​? extends V> remappingFunction)
    If the specified key is not already associated with a value or is associated with null, associates it with the given non-null value. Otherwise, replaces the associated value with the results of the given remapping function, or removes if the result is null

    Lots of different possible corner/edge cases if you put NULL in as values for any keys, not just about which maps you can do that in or be copied into without failing, but what different API calls will do in those cases...

    A lot of stuff you don't need to remember if you avoid NULL values.  Some of the complexity may be because you might feel the need for a mapping/remapping function to return NULL sometimes, maybe that is something to think about.
     
    Jesse Silverman
    Saloon Keeper
    Posts: 1327
    40
    Eclipse IDE Postgres Database C++ Java
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator
    In my Radical Anti-Null Diatribe I didn't mention that null is also bad in Queue and Deque use.
    Most people don't want to throw and catch exceptions unnecessarily (I hope) so the versions of methods that return NULL when called on an empty collection are popular.

    Then THIS happens if you allow null values to go in:
    jshell> iq
    iq ==> [4, 6, 8, 10, 12]

    jshell> iq.getFirst()
    $46 ==> 4

    jshell> iq.getFirst()
    $47 ==> 4

    jshell> iq.getLast()
    $48 ==> 12

    jshell>  iq.add(null)
    $51 ==> true

    jshell> iq
    iq ==> [4, 6, 8, 10, 12, null]

    jshell> iq.pollLast()
    $53 ==> null

    jshell> iq.add(0, null)

    jshell> iq.poll()
    $55 ==> null

    jshell> iq.poll()
    $56 ==> 4


    Yucko!!  Nulls are bad news in more contexts than Set and Map as well...say No to nulls!
     
    Campbell Ritchie
    Marshal
    Posts: 74054
    332
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator
    Sir Tony Hoare has called the invention of null his million‑dollar mistake, his sixty‑four‑million‑dollar mistake, his billion‑dollar mistake, etc.
     
    Jesse Silverman
    Saloon Keeper
    Posts: 1327
    40
    Eclipse IDE Postgres Database C++ Java
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator

    Campbell Ritchie wrote:Sir Tony Hoare has called the invention of null his million‑dollar mistake, his sixty‑fourꀑmillion‑dollar mistake, his billion‑dollar mistake, etc.



    He's been around so long the value of his mistake just keeps doubling every few years!
     
    Campbell Ritchie
    Marshal
    Posts: 74054
    332
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator

    Jesse Silverman wrote:. . . the value of his mistake just keeps doubling every few years!

    He says that is true, and it is no joke; the number of programmers and the amount of code suffering problems caused by null increases year on year, and the total damage it does increases year on year, maybe exponentially.
     
    Jesse Silverman
    Saloon Keeper
    Posts: 1327
    40
    Eclipse IDE Postgres Database C++ Java
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator
    I felt like I was hijacking the thread to some extent with the extensive "Nulls are pretty risky" diatribes, but the number of problems you almost completely avoid if you can avoid storing nulls in the data structures that do allow them is not small.  Additionally, they could be code or logic dependent and might not show up in initial testing.  Having a rule about thinking three times before stuffing a null into a Set or Map or Queue is probably warranted.

    Related, but not the same question -- is Optional part of "beginning basic Java" now, I wonder?
     
    Paul Clapham
    Sheriff
    Posts: 26791
    82
    Eclipse IDE Firefox Browser MySQL Database
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator

    Jesse Silverman wrote:He's been around so long the value of his mistake just keeps doubling every few years!



    This is the Moore's Law for Hoare's Mistake?
     
    Campbell Ritchie
    Marshal
    Posts: 74054
    332
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator
    Not the Hoare's Law for Moore's Mistake, then?
     
    Jesse Silverman
    Saloon Keeper
    Posts: 1327
    40
    Eclipse IDE Postgres Database C++ Java
    • Mark post as helpful
    • send pies
      Number of slices to send:
      Optional 'thank-you' note:
    • Quote
    • Report post to moderator

    Campbell Ritchie wrote:...but the HashMap documentation allows a key to be null or to map to null. Agree it would have been better to ban nulls, as the old implementation did, and as Map#copyOf() and Map#of(...) do. Maybe allowing nulls is a mistake, and I was pleased to hear Jesse say that. Nasty case of violating the DRY principle.
    People might come unstuck when they interpret getting null as meaning there isn't a mapping that hasn't been totally removed. That is what the containSxxx() methods look for. Maybe null is a valid value.
    ...
    The old implementation prohibited nulls, which meant, rather than recording no value one had to delete the key and the whole Entry. Other implementations permit inactivating a mapping by putting null.



    At about 47:20 of this old video (which I still found excellent enough to watch a second time--there are so many enhancements which look like mere conveniences but are SO MUCH MORE THAN THAT):
    https://www.youtube.com/watch?v=q6zF3vf114M

    Stuart mentions that NULLS cause lots of problems and they have been excluding them as possible values as much as they possibly can without breaking legacy code.

    This thread included me trying to mention all the places NULL will kill you or complicate things, the resulting list edited neatly would be about three times the size of the language spec for Modula 3.
    I just can't remember that many rules, so "Just Say No to NULLS in Collections" seems like great advice.
     
    A berm makes a great wind break. And we all like to break wind once in a while. Like this tiny ad:
    Thread Boost feature
    https://coderanch.com/t/674455/Thread-Boost-feature
    reply
      Bookmark Topic Watch Topic
    • New Topic