• 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
  • Tim Cooke
  • paul wheaton
  • Jeanne Boyarsky
  • Ron McLeod
Sheriffs:
  • Paul Clapham
  • Liutauras Vilda
  • Devaka Cooray
Saloon Keepers:
  • Tim Holloway
  • Roland Mueller
Bartenders:

hashCode(), equals(): Got question right, but for the wrong reasons

 
Ranch Hand
Posts: 434
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I came across a question in Enthuware that I got correct, but when I read the explanation I realized that I do not have a firm grasp for the concept. Also viewed other messages on the forum that address the SAME question, but I am still not grasping. Please see below.




My understanding of the code:
Line 28 will print 10 because new Book object was created, it's ISBN was set to 111, and it was placed on the bookshelf

Line 30 reassigns the reference variable b to a newly created Book object whose object is also set to 111. However it is not placed in the bookshelf so when Line 31 is executed it will throw an exception due to a null reference.

Below is Enthuware's explanation with my [commentary in bold, within brackets]:


There are multiple concepts involved here:

1. Observe that Book class has overridden equals() method but hasn't overridden hashCode() method. The way equals method is coded here makes two Book objects equal if their isbn is equals. However, their hashCode value will be different because Object class's hashCode method returns unique hashcode for every object.

2. In the main method, a Book object is stored in BookStore. The same Book object is being used to retrieve the numberofcopies value. Since the object that was stored in the Map and the object that was used to retrieve the value from the map are the same object, their hashCode will be the same and hence map will be able to find it among its name-value pairs. Thus, this will print 10.

3. Next, a new Book object is created. Note that its hashcode will be different from the previously created book object. But as per equals() method of Book, they are equal because their isbn are same. When you try to use the new Book object to find the value, map will not find anything because of a different hashcode. So map.get() will return null.[what difference does this make if the newly created Book object isn't even placed in BookStore. Even if the newly created Book object has ISBN if 112, it seems that map.get() will return null since it wasn't placed in BookStore]

4. The return type of the method is an int (and not an Integer). But map.get always returns an object (not a primitive.) So auto-unboxing will try to convert null into an int, which will throw a NullPointerException.[what method are they referring to?]

5. There is no warning at compilation because all the operations are valid.

 
Bartender
Posts: 2700
IntelliJ IDE Opera
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
3. You are correct.
4. They are referring to this method:
Because the return type of the method is an int and the return value from map.get() is an Integer auto(un)boxing will try to convert the Integer to an int (by calling Integer.intValue()). But because map.get() returns null in this case you'll get a NullPointerException because you're trying to invoke a method on a null reference.
 
Greenhorn
Posts: 22
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Hi,

I am confused with the third option. I included hashcode method for the above code and when I executed it, the answer was 10 10.



Even if second book object is not added to bookstore, reference variable 'b' refers to it in statement 45. Therefore, the key 'b' in map now refers to the second book object . Since the hashcode and equals method are overridden and they return equal( same hashcode and isbn value), map finds the value
(number of copies )added for the first Book object same for second Book Object and hence returns 10, 10.
If isbn is now changed from "111" to "112" ie the code returns 10 and a null pointer exception. This is because equals method return a false value( isbn doesn't match) and now map can't find the values for the second Book Object and number of copies is returned as null.

So for Sandra's code, I do not think adding or not adding second object to the map is the real reason for the null pointer exception. I think defining equals and hashcode methods are the real key to the issue. When hashcode method is not overridden(hash codes are different) or ISBN value is changed from 111 to 112(equals method return false), map cannot find any value(number of copies) for the second object, therefore map.get() returns null. Can someone correct me, if my logic is wrong.

-
Gouri
 
Wouter Oet
Bartender
Posts: 2700
IntelliJ IDE Opera
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Gouri Kumari wrote:[...] Even if second book object is not added to bookstore, reference variable 'b' refers to it in statement 45. Therefore, the key 'b' in map now refers to the second book object .

No you're changing the local copy of the reference but you're not changing anything in the Map.

Gouri Kumari wrote: So for Sandra's code, I do not think adding or not adding second object to the map is the real reason for the null pointer exception. I think defining equals and hashcode methods are the real key to the issue. When hashcode method is not overridden(hash codes are different) or ISBN value is changed from 111 to 112(equals method return false), map cannot find any value(number of copies) for the second object, therefore map.get() returns null. Can someone correct me, if my logic is wrong.

Even with a proper implementation of the hashcode and equals methods it will throw a NullPointerException if you call getNumberOfCopies() with a Book that is not in the Map. I explained the reason for that Exception in my previous post.
 
Gouri Kumari
Greenhorn
Posts: 22
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

I am not still clear why map can't reflect the change.


No you're changing the local copy of the reference but you're not changing anything in the Map.



Isn't this explanation valid if we declare 'b' as a new Book reference variable. For example: if we add a new method 'work' as below to class TestClass3 and try to invoke it using an object of TestClass3, the key in map won't reflect the change because 'b' defined is local to work method and not the one added to the map.




In the below code 'b' is declared only once and the same reference variable is made to point to a second object. Since the map contains the same copy of reference variable even if it is local to main, must not the change done in method 'main' reflect in map also.






Even with a proper implementation of the hashcode and equals methods it will throw a NullPointerException if you call getNumberOfCopies() with a Book that is not in the Map. I explained the reason for that Exception in my previous post.


I agree to this explanation. Even if with proper implementation of hashcode and equals method the getNumberOfCopies() will return a null value and when unboxed using Integer.intValue(), a null pointer exception is thrown.




 
Sandra Bachan
Ranch Hand
Posts: 434
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Reading this dialogue made something in my mind click. Please read my comments embedded in the TestClass code lines 26 to 31:




So the b in BookStore map points to memory address 0xAD34FC. We never passed in the reference that points to NEW memory address 0x2DF345A that contains Book Object of ISBN "111". And even if we did, how would the program react? Without any implementation of hashCode() the program thinks that all objects are different even though we overridden equals() that defines an individual Book object based on its ISBN. Whereas if we implement hashCode() such that it returns 7





Please critique my understanding....
 
Wouter Oet
Bartender
Posts: 2700
IntelliJ IDE Opera
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Gouri Kumari wrote:Isn't this explanation valid if we declare 'b' as a new Book reference variable. For example: if we add a new method 'work' as below to class TestClass3 and try to invoke it using an object of TestClass3, the key in map won't reflect the change because 'b' defined is local to work method and not the one added to the map.

No because when a method is invoked with a parameter then it is a copy of the original reference (in case of Object's). So you're changing the copy of the original reference.

@Sandra Bachan
Line 31 will cause an exception.

Let me give a reference example of the this code:
 
Gouri Kumari
Greenhorn
Posts: 22
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

No because when a method is invoked with a parameter then it is a copy of the original reference (in case of Object's). So you're changing the copy of the original reference.



I think you have got me wrong here. I am not invoking a method by passing a copy of reference variable from main method. I am just creating a method in which a new Book reference variable 'b' which points to a new Book Object() is created. There is no connection between this reference variable and the reference variable 'b' in main method.




I will rephrase my doubt:


When we assign a new object to the reference variable 'b' in the main method the memory address stored in 'b' is changed from 0xAD34FC to 0x2DF345A. Since map contains a original copy of the reference variable 'b' in main method doesn't the change in memory address reflect in map also and hence reference variable in map points to the new Book Object().

b = new Book(); // The b reference is changed to point to a different Book. However because the BookStore got a copy of the original reference, that one still points at the other book.


I got Wouter's point of view. Is it like map implementation works differently, ie once a copy of reference is added to a map, any further changes in the original reference will not be reflected in the map.







 
Wouter Oet
Bartender
Posts: 2700
IntelliJ IDE Opera
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
@Sandra are you grasping the concept now?

Gouri Kumari wrote:I think you have got me wrong here. I am not invoking a method by passing a copy of reference variable from main method.


Yes you are: bs.addBook(b,10); That method gets a copy of the reference.

Gouri Kumari wrote:Is it like map implementation works differently, ie once a copy of reference is added to a map, any further changes in the original reference will not be reflected in the map.

Any method works this way. Any reference variable from a parameter is a copy of the original one.
 
Gouri Kumari
Greenhorn
Posts: 22
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
The copy of original Book reference variable in main method is passed to the addBook method. The local Book reference variable 'b' declared in addBookMethod now points to the first book object and is added as key to the map. Any further change in Book reference variable in 'main' method will not reflect in map because changes are local to main method.
Thanks Wouter for explaining it. It took me sometime to understand completely. I have to go back to basics again.

-
Gouri
 
Gouri Kumari
Greenhorn
Posts: 22
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Hi,
I am sorry to bring this post again but with my above flaw in understanding I deviated away from Sandra's original doubt


3. Next, a new Book object is created. Note that its hashcode will be different from the previously created book object. But as per equals() method of Book, they are equal because their isbn are same. When you try to use the new Book object to find the value, map will not find anything because of a different hashcode. So map.get() will return null.[what difference does this make if the newly created Book object isn't even placed in BookStore. Even if the newly created Book object has ISBN if 112, it seems that map.get() will return null since it wasn't placed in BookStore]



To me overriding hashcode method still plays a role in this code.



In the second println statement we are passing the copy of reference of new Book Object() to getNumberOfCopies method. This reference is used for search in the map. Since the hashcode method is not overridden, map cannot locate any equivalent object and map.get(b) returns null. The getNumberOfCopies method return an int value, therefore returning a null value results in null pointer exception as Wouter explained.
If the hashcode method has been overridden, map locates a match and finds the second object stored in the reference passed from main method ( ISBN "111") and object already added to map (ISBN "111") as equal and return number of copies as 10.

Quote from K&B book: If you want objects of your class to be used as keys for a hashtable(or as elements in any data structure that uses equivalency for searching for or retrieving an object) then you must override equlas() so that two different instances can be considered equal. When you want to fetch an object(or for a hash table , retireve the associated value for that object), you have to give the collection a reference to an object that the collection compares to the objects it holds in the collection. As long as the object(stored in the collection) you are trying to search for has the same hashcode as the object you are searching for, then the object will be found.


 
With a little knowledge, a cast iron skillet is non-stick and lasts a lifetime.
reply
    Bookmark Topic Watch Topic
  • New Topic