Reputation: 11
It prints true for both of the following print statements in the sample code. I understand, its as per the logic of equals method of String class as:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
...
}
But I am unable to figure out how their hashcode remains unchanged. Does the condition, this == anObject has any relationship with the hashCode method of String class? If yes then how are they equal.
Please help me to understand this.
It is true that value of a string can be modified through reflection(where it losses its immutability nature). But in this case the hashcode remains unchanged. Why?
import java.lang.reflect.Field;
public class StringHacker {
public static void main(String[] args) throws Exception {
String myMonth = "January";
char[] yourMonth = {'M', 'a', 'y'};
Field value = String.class.getDeclaredField("value");
value.setAccessible(true);
value.set(myMonth, yourMonth);
System.out.println(myMonth.equals("January"));
System.out.println(myMonth.equals("May"));
}
}
The output is:
true
true
Upvotes: 0
Views: 87
Reputation: 5034
You mention hashcode in your question, but never call it nor display it's value, nor compare hoshcode values. So to answer your question :
Does the condition, this == anObject has any relationship with the hashCode method of String class?
The answer is an emphatic "no" (other, of course, than the obvious case that two references to the same object will obviously be calling the same method and get returned the same result). Likewise, hashcode() is also not called/considered by the equals() method.
So let's consider ==, equals(), and hashcode(), and how these play out in your example. Firstly, though, it must be mentioned that you are using reflection in a way that it was never intended to be used. There are situations where calling value.set(object, value)
is valid and necessary - but changing the value of an immutable class like "String" is not one of them. Upshot is that it's not surprising to get weird results by doing things like that.
Let's start by restating that every object (such as a String) lives at its own location in the computer's memory. For example, consider code like :
String myName = "Fred";
String yourName = "Fred";
String databaseName = fetchNameFromDatabase(); // returns "Fred"
boolean mineIsYours = (myName == yourName); // true
boolean mineIsDatabases = (myName == databaseName); // false
boolean mineEqualsDatabases = myName.equals(databaseName); // true
All 3 Strings will have the same value "Fred" - but there's a neat trick. When the Java compiler compiles the program, it will load all hard-coded strings into the .class
file. Since Strings are immutable, it saves some space by creating unique values in a "String pool" - so for my example, "Fred" will only be created ONCE, and myName
and yourName
will both be pointing to the SAME instance in memory - hence mineIsYours
will be true
.
Strings created dynamically (eg read from the database) would not use this String pool, so will be different instances even though they may have the same value - hence the importance to test equality using equals()
rather than ==
.
Can you now see what's happening in your program ? Let's look at a few specific lines :
String myMonth = "January";
"January" is a hard-coded constant, so it's put in the String pool, and myMonth is pointing to the location of that instance in memory.
value.set(myMonth, yourMonth);
The value of myMonth - ie, the value of that instance in memory that myMonth is pointing to - is changed to be "May".
System.out.println(myMonth.equals("January"));
Calls "equals" on myMonth, passing in the instance of the hard-coded String that the Java compiler put into the String pool for "January". However, this instance is THE SAME INSTANCE that myMonth was initialised to (remember my variable mineIsYours
was true) !! Yes, THE SAME INSTANCE that you changed the value of to be "May".
So, when you changed myMonth's instance value in the String pool from "January" to "May", you didn't just change it for that one myMonth variable, but for EVERY hard-coded "January" value in the program !
System.out.println(myMonth.equals("May"));
The value of the instance that myMonth is pointing to has been changed to "May", so this is true.
So where is hashcode()
used in all this ? As I mentioned earlier, it isn't. Not at all.
From your question, I'm wondering : is your understanding that two objects are equal if their hashcodes match ? If so, no - not at all. There is NO - repeat NO - requirement that hashcodes be unique, which means the idea of "equal if hashcodes match" obviously fails.
The purpose of hashcode()
is to give a wide spread of values for different instances of the class. This is used in structures like HashMap
,etc to put objects into different "buckets" for quick retrieval.
The implicit connection between equals()
and hashcode()
is that :
hashcode()
calculation should use the
exact same fields as equals()
- no more and no less.Upvotes: 0
Reputation: 748
The hashcode does not change because String
is an immutable class.
That means by contract its value will not change. As the same value must always have the same hashcode, there is no need ever to change the hashcode. Even worse, an object with a hashcode changing over time may get you in big trouble, e.g. when dealing with Set
and Map
.
An object must not change its hashcode!
If you alter a string's value via reflection you're actively breaking the contract and thus causing undefined, chaotic and possibly catastrophic behaviour.
Upvotes: 0
Reputation: 719307
But in this case the hashcode remains unchanged. Why?
The answer is that String::hashCode
caches its result in a private field. So if you do this:
String s = /* create string */
int hash = s.hashcode();
/* use reflection to mutate string */
int hash2 = s.hashCode();
you will find that hash
and hash2
are the same value. This is just one more reason why it is a bad idea to use reflection to mutate strings.
(But if you read the code for String
you can see how hashCode
is implemented and then use reflection to clear the cached hashcode value.)
Upvotes: 2