Issues with Java HashMap and key Object I rolled myself - java

So, I'm trying to use a HashMap to map my own Object to a String value. My object is below (with some code removed for brevity)
public class RouteHeadsignPair {
String route;
String headsign;
public RouteHeadsignPair(String n_route, String n_headsign) {
route = n_route.toLowerCase();
headsign = n_headsign.toLowerCase();
}
public String getRoute () {
return route;
}
public String getHeadsign() {
return headsign;
}
public boolean equals(RouteHeadsignPair other) {
return(other.getRoute().equals(route) && other.getHeadsign().equals(headsign));
}
public int hashCode() {
return(route.hashCode());
}
}
I'm mapping a bunch of these objects to Strings by loading data from a text file. Later on, based on (independent) user input, I try to query the HashMap using a RouteHeadsignPair Object. containsKey() returns false and get() returns null, as if I had never added the key into the map. But, bizarrely, if I iterate over the map using the below code (where newKey is a RouteHeadsignPair made from user input)
RouteHeadsignPair foundKey = null;
Iterator<RouteHeadsignPair> keysInMap = routeHeadsignToStopIdMap.keySet().iterator();
while(keysInMap.hasNext()) {
RouteHeadsignPair currKey = keysInMap.next();
if(currKey.equals(newKey)) {
System.err.println("Did find a key with an equals() == true!");
foundKey = currKey;
}
}
System.err.println("Value in map? " + routeHeadsignToStopIdMap.containsKey(newKey) + "( hashcode = " + newKey.hashCode() +
", equals = " + newKey.equals(foundKey) + ")");
System.err.println("foundKey in map? " + routeHeadsignToStopIdMap.containsKey(foundKey) + "( hashcode = " + foundKey.hashCode() +
", equals = " + foundKey.equals(newKey) + ")" );
I apologize for the code formatting, it's late and I'm getting cranky
I get the following output
Did find a key with an equals() == true!
and then
Value in map? false( hashcode = 1695, equals = true)
foundKey in map? true( hashcode = 1695, equals = true)
So, if I iterate over the keys and look for keys that return equals(), I do find one, and the hashCode() is the same for both of these. If the hashCode() is the same for newKey and foundKey and foundKey.equals(newKey) returns true, shouldn't HashMap.get(key) return a value and containsKey() return true? What am I doing wrong here?

You're not overriding Object.equals - you're overloading it because of the parameter type. Your diagnostic code calls your overload, but the map code doesn't (as it doesn't know about it).
You need a method with a signature of
public boolean equals(Object other)
If you use the #Override annotation you'll get an error if you fail to override something properly.
You'll need to check whether other is an instance of RouteHeadSignPair first, then cast. If you make the RouteHeadSignPair class final, you won't need to worry about whether or not it's the exact same class, etc.
Note that your hash codes will collide unnecessarily, by the way - if you use both the route and the headSign hashes to generate your hash code, it may help your map lookups to be more efficient. (If there are several instances with the same route but different head signs, it's useful if the map doesn't have to check for equality on all of them when looking up a key.)

Related

Why is my HashMap.containsKey(myKey) always returning false? [duplicate]

I am trying to locate a key in a HashMap. I can print the selected key by using 'get' but when I use 'containsKey' in an if statement, it is not found.
I KNOW the key is present in the Map but it keeps returning false. Any ideas people?
My code:
public static boolean checkLowerStructuralSupport(Location location) {
boolean hasSupport = false;
Location supportingLocation = new Location(location.getX(), location.getY(), location.getZ() - 1);
System.out.println(_levels.get(supportingLocation.getZ()).getLevelSites2().get(supportingLocation)); //works
if (_levels.get(supportingLocation.getZ()).getLevelSites2().containsKey(supportingLocation)) {
hasSupport = true;
} else {
hasSupport = false;
}
return hasSupport;
}
Here is the code for the Location class:
public class Location {
protected int _x;
protected int _y;
protected int _z;
public Location(int xAxis, int yAxis, int zAxis) {
this._x = xAxis;
this._y = yAxis;
this._z = zAxis;
}
public void equals() {
//not implemented yet
}
public void HashCode() {
//not implemented yet
}
public String toString() {
String locationString = Integer.toString(_x) + Integer.toString(_y) + Integer.toString(_z);
return locationString;
}
public void setX(int XAxis) {
this._x = XAxis;
}
public int getX() {
return this._x;
}
public void setY(int YAxis) {
this._y = YAxis;
}
public int getY() {
return this._y;
}
public void setZ(int ZAxis) {
this._z = ZAxis;
}
public int getZ() {
return this._z;
}
}
You must ensure that the Location class has properly implemented its hashCode() and equals(Object) methods (documentation). That is, if two Location objects are effectively equal, they should share a common hash code and their equals method should return true.
As descibed here, you have to override the equals(Object) method.
The reason why get(Object) is working is, that HashMap will calculate the Hash for your Location class and returns the Object the hascode points to.
containsKey(Object) calculates the hash key and gets the object the hash is pointed to. The object from the HashMap will compare to the Object you put in. For these comparison the equals method is used.
When you do not override he equals method, true is returned, when the object reference to the same instance.
From HashMap
/**
* Check for equality of non-null reference x and possibly-null y.
*/
static boolean eq(Object x, Object y) {
return x == y || x.equals(y);
}
From Object
public boolean equals(Object obj) {
return (this == obj);
}
From the javadoc of equals
The equals method for class Object
implements the most discriminating
possible equivalence relation on
objects; that is, for any non-null
reference values x and y, this method
returns true if and only if x and y
refer to the same object (x == y has
the value true).
Note that it is generally necessary to
override the hashCode method whenever
this method is overridden, so as to
maintain the general contract for the
hashCode method, which states that
equal objects must have equal hash
codes.
In Location class, make sure you are overriding hashCode and equals methods.
If you are, can you post them?
containsKey uses the method equals to compare the param with the entries in the key set. So the Location class needs to have a equals method that is good. The default equals method in java.lang.Object only returns true when both objects are the same object. In this case you probably have 2 different instances that needs to be compared and need a custom equals method.
The only thing I can think of that will cause this is if the state of supportingLocation is somehow being mutated between the get(...) call and the containsKey(...).
Assuming the code snippet you posted is the exact code that's causing problems, the only place this could occur is if one of Location#getZ(...), Location#hashCode() or Location#equals(Object) mutates the state of Location (or the Location constructor, or one of these methods starts a thread that randomly changes the state of the Location instance, but I think we can rule that out).
Could you verify that none of the methods above are changing the state of the supportingLocation instance? While I am not familiar with the Location class itself, I'd venture to guess that a class like that would ideally be immutable.
Edit:
To clarify, when I say that Location#getZ() etc aren't mutating the Location, what I mean is:
Location x = new Location(1,2,3);
Location y = new Location(1,2,3);
boolean eq1 = x.equals(y);
int hash1 = x.hashCode();
x.getZ(); // this should *not* mutate the state of x
boolean eq2 = x.equals(y);
int hash2 = x.hashCode();
In the end, eq1 should be equal to eq1, and hash1 should be equal to hash2. If this is not the case, getZ() is mutating the state of x (or equals, or hashCode, or worse, those methods are completely off), and will result in the behavior you observed.
To avoid problems, your equals() and hashCode() methods should be consistent and conform to the requirements (as noted elsewhere).
Additionally, hashCode() should not rely on mutable members, otherwise your calculated hash code can change, and this affects the internal workings of the HashMap. That will reveal itself in an inability to retrieve stuff from Hash* collections.
Take a peak at the source code for the HashMap implementation. Both get and containsKey use the hasCode() and equals() methods of your key object.
The only real difference, and as was pointed out, it is a trivial null check, is in the comparisons:
get:
((k = e.key) == key || key.equals(k))
containsKey:
((k = e.key) == key || (key != null && key.equals(k)))
where e is of type Entry for a HashMap.
So, if you do not have a strong implementations of hashCode() and/or equals() you will have a problem. Additionally, if your keys were mutated (I see that you did not declare the class fields final) you could have an issue.
Take the following example:
public class HashMapTest {
static class KeyCheck {
int value;
public KeyCheck(int value) { this.value = value; }
public void setValue(int value) { this.value = value; }
#Override public int hashCode() { return value; }
#Override public boolean equals(Object o) {
return ((KeyCheck)o).value == this.value;
}
}
public static void main(String args[]) {
HashMap<KeyCheck, String> map = new HashMap<KeyCheck, String>();
KeyCheck k1 = new KeyCheck(5);
KeyCheck k2 = new KeyCheck(5);
map.put(k1, "Success");
System.out.println("Key: " + k1 + " Get: " + map.get(k1) +
" Contains: " + map.containsKey(k1));
System.out.println("Key: " + k2 + " Get: " + map.get(k2) +
" Contains: " + map.containsKey(k2));
k1.setValue(10);
System.out.println("Key: " + k1 + " Get: " + map.get(k1) +
" Contains: " + map.containsKey(k1));
System.out.println("Key: " + k2 + " Get: " + map.get(k2) +
" Contains: " + map.containsKey(k2));
}
}
This will print out:
Key: HashMapTest$KeyCheck#5 Get: Success Contains: true
Key: HashMapTest$KeyCheck#5 Get: Success Contains: true
Key: HashMapTest$KeyCheck#a Get: null Contains: false
Key: HashMapTest$KeyCheck#5 Get: null Contains: false
As you can see, in this case the mutability caused the hashCode() to change, which ruined everything.
Both get() and containsKey() are using the Location class's hashCode() method. The equals() method isn't called unless there is a hash collision. (thus, HashMap's get() won't use equals() in every situation.)
For your Location class, did you by chance happen to implement your own version of hashCode()? The hashCode() method should be implemented carefully. Joshua Bloch described all the details in the book Effective Java, portions of which are online... I'll go find the link to those sample chapters: Effective Java Sample Chapters. You want chapter 3.
As I asked in the comment to the question, Where does your _levels variable come from? I don't see it declared inside that method and your naming (underscore prefix, are you importing that convention from some other language?) suggests that it "lives" outside this method. Perhaps other code is changing it during execution? Please let us know when you solve it; the suspense is killing me.
i think sometime you need the hash code and sometimes not so i think in this way you can turn of the hash code checking when you want buy changing the hash code for all objects you want to 0
public class sample(){
#JsonIgnore
private int hashCode = super.hashCode();
public void setHashCode(int hashCode){
this.hashCode = hashCode;
}
#Override
public int hashCode(){
return this.hashCode;
}
#Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final ReflectObject other = (ReflectObject) obj;
if (this.hashCode != other.hashCode) {
return false;
}
return true;
}
}

Check if Object exists in ArrayList in Java

I have the following List of objects:
private List<Object> teamlist = new ArrayList<Object>();
And I'm adding objects to the list like so:
teamlist.add(new MCWarTeam(args[0], joinkey));
Now the objects in the list have no name, but can be referenced by using the list, right? Before I add a new element to the list, how can I check if an object with a certain attribute already exists? This is the constructor of the Objects:
public MCWarTeam(String teamname, String joinkey){
this.teamname = teamname;
this.joinkey = joinkey;
}
I want to check if there already is a team with the name teamname. Alternatively, is there a better way to store the Objects? Before, I just used a HashMap to add the teamname and joinkey and it worked just fine, but figured using Objects instead would be a better way to do it.
Here is the important code for the event handler:
else if (cmd.getName().equalsIgnoreCase("createTeam")) {
if (args.length > 0 && args.length < 3) {
String joinkey = "";
if (args.length > 1)
joinkey = args[1];
String teamname = args[0];
MCWarTeam newTeam = new MCWarTeam(teamname, joinkey);
if (!teamlist.containsKey(teamname)) {
teamlist.put(teamname, newTeam);
sender.sendMessage("Created new team \"" + teamname + "\" with join key \"" + joinkey + "\" successfully! Teams:");
sender.sendMessage("All teams:");
for (String key : teamlist.keySet()) {
sender.sendMessage(key);
}
} else
sender.sendMessage("Team already exists!");
return true;
}
return false;
}
else if (cmd.getName().equalsIgnoreCase("joinTeam")) {
if (args.length > 0 && args.length < 3) {
String joinkey = "";
if (args.length > 1)
joinkey = args[1];
String teamname = args[0];
if (teamlist.containsKey(teamname)) {
String teamKey = teamlist.get(teamname).getJoinKey();
if (joinkey == teamKey) {
teamlist.get(teamname).addPlayer(playername);
Bukkit.broadcastMessage("MCWar: " + playername + " joined Team \"" + teamname + "\" successfully!");
} else
sender.sendMessage("Join key incorrect!");
} else {
sender.sendMessage("Team doesn't exist! Teams:");
for (String key : teamlist.keySet()) {
sender.sendMessage(key);
}
}
return true;
}
return false;
}
Basically, if it returns false, the user will get a message explaining the correct usage of the command he entered.
Java's List<T> has a boolean contains(Object) method, which is handy for situations when you wish to avoid duplicates:
if (!teamlist.contains(newTeam)) {
teamlist.add(newTeam);
}
MCWarTeam class must implement equals in order for this to work. When you override equals, you must also override hashCode.
#Override
public boolean equals(Object obj) {
if (!(obj instanceof MCWarTeam)) {
return false;
}
MCWarTeam other = (MCWarTeam)obj;
return teamname.equals(other.teamname)
&& joinkey.equals(other.joinkey);
}
#Override
public int hashCode() {
return 31*teamname.hashCode()+joinkey.hashCode();
}
I'm just looking to check if an Object with the same teamname already exists, but not care about the joinkey?
If joinkey is not part of your object's state that influences equality, it is usually not a good idea to keep it as part of the object as a field. For example, if joinkey is something transient which you use to "connect" teams to other things, making a HashMap<String,MCWarTeam>, using joinkey as the key to the map, and removing joinkey from MCWarTeam should be a good idea.
Based on the description and your comments to other answers, it seems like a good idea to not use a List, but instead store your data in a Map<String, MCWarTeam>, which maps team names into MCWarTeam objects:
private Map<String, MCWarTeam> teams = new HashMap<>();
You can add a team, checking whether a team with the same name already exists, like this:
String teamName = args[0];
if (!teams.containsKey(teamName)) {
teams.put(teamName, new MCWarTeam(teamName, joinKey));
} else {
// do what you want when the team name was already in the map
}
Retrieving an MCWarTeam object based on team name, e.g. for accessing the joinKey attribute, is easy:
String joinKey = teams.get(teamName).getJoinKey();
Note that using this approach, you shouldn't implement equals or hashCode in MCWarTeam, because you aren't gonna need it; as your map keys are team names, containsKey operates on String objects which already have well-defined equals and hashCode semantics.
In order to search for an MCWarTeam instance in the ArrayList, you'll first have to override equals in order to define what it means for two MCWarTeam instances to be equal to each other. Then you can use indexOf(team) or contains to determine whether a instance is in the List.
However, such a search would take linear time, so a HashSet may be better for your needs (for that purpose you'll need to override both equals and hashCode, and you'll be able to find if an object is in the Set in constant time).
If you implement MCWarTeam equals method properly, then contains should tell you if the object exists.
boolean exists = teamlist.contains(member);
And as #Eran mentioned a HashSet would give you O(1) lookup where list contains is O(n), the only thing is that HashSet doesn't allow duplicates.
And Yes, use the actual type rather than Object
List<MCWarTeam> teamlist = new ArrayList<>();

Hashcode deceives me sometimes

I need clarification whether my approach is right or wrong any modifications required.
Let me explain clearly. I will have a excel file in which there will be country code country name years(mm/yyyy) as extra 10 columns
countrycode country Name 12/2000 11/2000 10/2000 09/2000 08/2000 07/2000 06/2000 05/2000 04/2000 03/2000 02/2000 01/2000
IND India 10.1 10.2 10.3 10.4 10.5 10.6 10.7 10.8 10.9 11.1 11.2 11.3
USA Uinted States 8.1 8.2 8.3 8.4 8.5 8.6 8.7 8.8 8.9 9.1 9.2 9.3
In a row if anyof the price is repeated for that particular year and country , i need to show message as Duplicate present in Excel file.
For the above , i implemented by this way. For a VO i override the hashCode() with the hashcode of (coutrycode + year + price) and equals method too and
while inserting in database i pass this VO to HashSet and I eliminate duplicate and compare the size of original list size with HashSet size.
But sometime if there is unique price also I am getting message as duplicate.
Please suggest me my approach is right or wrong or another way I can implement.
Buddy you have taken the right thought and approach to solve the problem but just missing a little edge (information) to solve the problem correctly.
I would like to provide a little hint, that I believe can help and rectify the problem and understand the basics really very well.
If you look at the documentation (or the source code) of hashCode for the String and Double variables, it states
STRING
Returns a hash code for this string. The hash code for a String object is computed as
s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
using int arithmetic, where s[i] is the ith character of the string, n is the length of the string, and ^ indicates exponentiation. (The hash value of the empty string is zero.)
Returns: a hash code value for this object.
DOUBLE
Returns a hash code for this Double object. The result is the exclusive OR of the two halves of the long integer bit representation, exactly as produced by the method doubleToLongBits(double), of the primitive double value represented by this Double object. That is, the hash code is the value of the expression:
(int)(v^(v>>>32))
where v is defined by:
long v = Double.doubleToLongBits(this.doubleValue());
Returns: a hash code value for this object.
So the hashCode() function returns a unique value in most case, but there are so many cases when the it returns the same int value for the two objects.
I think you are also getting caught in the same scenario.
A little more hint, you can use the HashMap<Integer,List<String>> where the Integer value is hashCode as you calculated and the List<String> is the collection of actual value got by forming the String from coutrycode + year + price .
And the last part is comparison, you can get the List<String> at the calculated hashCode() of new value and check if the same String value do exists in the List.
Hashbased collections depends on the hashcode() and equals() methods to correctly identify duplicates. If you modify these to fit exactly one usecase you are probably likely to have all sorts of side-effects in other use cases.
To say it more explicitly. If you change the methods of your VO to use only a subset of the data, you are likely to encounter unforeseen problems some where else where you might store VOs in hashbased collections.
You should keep hashcode() and equals() consistent with data equality, i.e. using all attributes for tests, as suggested in many sources (Source generators in eclipse, #EqualsAndHashcode annotations from Lombok, 'Effective Java' by Joshua Bloch, etc.).
In your explicit case you could create a specific wrapper to calculate your hashcodes and equality based on the subset.
As an example:
public void doit(List<VO> vos) {
Set<VOWrapper> dups = new HashSet<>();
for (VO vo : vos) {
if (dups.contains(new VOWrapper(vo))) {
System.out.println("Found a duplicate");
} else {
dups.add(new VOWrapper(vo));
// Process vo
}
}
}
Based on this VO
#Data // Lombok generates getters/setters/equals/hashcode (using all fields)
public class VO {
private String countrycode;
private String country;
private int month;
private int year;
private double price;
}
And this wrapper
public class VOWrapper {
private final VO vo;
public VOWrapper(VO vo) { this.vo = vo; }
// Equals method with only 3 fields used
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
VO other = ((VOWrapper) obj).vo;
if (vo.getCountry() == null) {
if (other.getCountry() != null)
return false;
} else if (!vo.getCountry().equals(other.getCountry()))
return false;
if (vo.getCountrycode() == null) {
if (other.getCountrycode() != null)
return false;
} else if (!vo.getCountrycode().equals(other.getCountrycode()))
return false;
if (Double.doubleToLongBits(vo.getPrice()) != Double.doubleToLongBits(other.getPrice()))
return false;
return true;
}
//Hashcode method with only 3 fields used
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((vo.getCountry() == null) ? 0 : vo.getCountry().hashCode());
result = prime * result + ((vo.getCountrycode() == null) ? 0 : vo.getCountrycode().hashCode());
long temp;
temp = Double.doubleToLongBits(vo.getPrice());
result = prime * result + (int) (temp ^ (temp >>> 32));
return result;
}
}
It is perfectly valid to write code like:
List<CountryInstance> list = ...;
Set<CountryInstance> set = new HashSet<CountryInstance>(list);
if(set.size() < list.size()){
/* There are duplicates */
For it to work you need value class instances. To create one you need to override equals and hashcode. Before you do that read What issues should be considered when overriding equals and hashCode in Java?
If you are just parsing all the values into Strings then your approach sounds logical to me.
I read your description. You seem to say that a unexpected duplicates are detected. So this really means that 'equals' method is not behaving as you expect I think. If 'hashCode' was incorrect, I think you would get the opposite problem (duplicate NOT detected).
If you are still experiencing issues then attach the implementation of 'hashCode' and 'equals' and it might help to quickly answer the problem.
One more thing. I assume that all sample countries are unique in the file? I mean no countries are duplicated later on in the file?

HashMap in Java does not work?

Here is my code:
double getRevenue(KeywordGroupKey key) {
Double r = revenueMap.get(key);
System.out.println(key + "\t" + key.hashCode());
for (KeywordGroupKey other : revenueMap.keySet()) {
System.out.println(other.toString() + "\t" + other.hashCode());
if(other.equals(key))
System.out.println("equals here...");
}
if(r == null)
r = 0.0;
return r;
}
and here is the output:
优惠打折,优惠券|"优惠券" 955095524
brand+点评团购|大众点评 726983298
brand-品牌词相关|团购网站大全 -713384514
brand-品牌词|点评网 2029153675
brand+点评团购|大众点评网 261410621
优惠打折,优惠券|"优惠券" 955095524
equals here...
So it is so strange that the value returned by the method is null, why dose this happen? Since there is a key in the revenueMap has the same hash code and equals with argument key.
Below is the current state of the revenueMap and key:
{brand+点评团购|大众点评=28.0, brand-品牌词相关|团购网站大全=49.9, brand-品牌词|点评网=21.0, brand+点评团购|大众点评网=167.0, 优惠打折,优惠券|"优惠券"=9.9}
优惠打折,优惠券|"优惠券"
My guess is that KeywordGroupKey is mutable and the key in question was modified after it was used as the key of the hash map.
If that's the case, then the key is in the wrong "bucket" in the HashMap and the get() method (or containsKey() method) will never find it (but iterating over the keys and/or entries will find it!).
For example, assuming there's a property foo in your class and that property is relevant to your hashCode() and equals() methods. The following code would "break" the HashMap:
KeywordGroupKey key = ...
revenueMap.put(key, someValue);
key.setFoo("differentValue");
Double result = revenueMap.get(key); // will return nothing!
Double result = revenueMap.get(originalValueOfKey); // will *also* return nothing!

Java HashMap get works but containsKey does not

I am trying to locate a key in a HashMap. I can print the selected key by using 'get' but when I use 'containsKey' in an if statement, it is not found.
I KNOW the key is present in the Map but it keeps returning false. Any ideas people?
My code:
public static boolean checkLowerStructuralSupport(Location location) {
boolean hasSupport = false;
Location supportingLocation = new Location(location.getX(), location.getY(), location.getZ() - 1);
System.out.println(_levels.get(supportingLocation.getZ()).getLevelSites2().get(supportingLocation)); //works
if (_levels.get(supportingLocation.getZ()).getLevelSites2().containsKey(supportingLocation)) {
hasSupport = true;
} else {
hasSupport = false;
}
return hasSupport;
}
Here is the code for the Location class:
public class Location {
protected int _x;
protected int _y;
protected int _z;
public Location(int xAxis, int yAxis, int zAxis) {
this._x = xAxis;
this._y = yAxis;
this._z = zAxis;
}
public void equals() {
//not implemented yet
}
public void HashCode() {
//not implemented yet
}
public String toString() {
String locationString = Integer.toString(_x) + Integer.toString(_y) + Integer.toString(_z);
return locationString;
}
public void setX(int XAxis) {
this._x = XAxis;
}
public int getX() {
return this._x;
}
public void setY(int YAxis) {
this._y = YAxis;
}
public int getY() {
return this._y;
}
public void setZ(int ZAxis) {
this._z = ZAxis;
}
public int getZ() {
return this._z;
}
}
You must ensure that the Location class has properly implemented its hashCode() and equals(Object) methods (documentation). That is, if two Location objects are effectively equal, they should share a common hash code and their equals method should return true.
As descibed here, you have to override the equals(Object) method.
The reason why get(Object) is working is, that HashMap will calculate the Hash for your Location class and returns the Object the hascode points to.
containsKey(Object) calculates the hash key and gets the object the hash is pointed to. The object from the HashMap will compare to the Object you put in. For these comparison the equals method is used.
When you do not override he equals method, true is returned, when the object reference to the same instance.
From HashMap
/**
* Check for equality of non-null reference x and possibly-null y.
*/
static boolean eq(Object x, Object y) {
return x == y || x.equals(y);
}
From Object
public boolean equals(Object obj) {
return (this == obj);
}
From the javadoc of equals
The equals method for class Object
implements the most discriminating
possible equivalence relation on
objects; that is, for any non-null
reference values x and y, this method
returns true if and only if x and y
refer to the same object (x == y has
the value true).
Note that it is generally necessary to
override the hashCode method whenever
this method is overridden, so as to
maintain the general contract for the
hashCode method, which states that
equal objects must have equal hash
codes.
In Location class, make sure you are overriding hashCode and equals methods.
If you are, can you post them?
containsKey uses the method equals to compare the param with the entries in the key set. So the Location class needs to have a equals method that is good. The default equals method in java.lang.Object only returns true when both objects are the same object. In this case you probably have 2 different instances that needs to be compared and need a custom equals method.
The only thing I can think of that will cause this is if the state of supportingLocation is somehow being mutated between the get(...) call and the containsKey(...).
Assuming the code snippet you posted is the exact code that's causing problems, the only place this could occur is if one of Location#getZ(...), Location#hashCode() or Location#equals(Object) mutates the state of Location (or the Location constructor, or one of these methods starts a thread that randomly changes the state of the Location instance, but I think we can rule that out).
Could you verify that none of the methods above are changing the state of the supportingLocation instance? While I am not familiar with the Location class itself, I'd venture to guess that a class like that would ideally be immutable.
Edit:
To clarify, when I say that Location#getZ() etc aren't mutating the Location, what I mean is:
Location x = new Location(1,2,3);
Location y = new Location(1,2,3);
boolean eq1 = x.equals(y);
int hash1 = x.hashCode();
x.getZ(); // this should *not* mutate the state of x
boolean eq2 = x.equals(y);
int hash2 = x.hashCode();
In the end, eq1 should be equal to eq1, and hash1 should be equal to hash2. If this is not the case, getZ() is mutating the state of x (or equals, or hashCode, or worse, those methods are completely off), and will result in the behavior you observed.
To avoid problems, your equals() and hashCode() methods should be consistent and conform to the requirements (as noted elsewhere).
Additionally, hashCode() should not rely on mutable members, otherwise your calculated hash code can change, and this affects the internal workings of the HashMap. That will reveal itself in an inability to retrieve stuff from Hash* collections.
Take a peak at the source code for the HashMap implementation. Both get and containsKey use the hasCode() and equals() methods of your key object.
The only real difference, and as was pointed out, it is a trivial null check, is in the comparisons:
get:
((k = e.key) == key || key.equals(k))
containsKey:
((k = e.key) == key || (key != null && key.equals(k)))
where e is of type Entry for a HashMap.
So, if you do not have a strong implementations of hashCode() and/or equals() you will have a problem. Additionally, if your keys were mutated (I see that you did not declare the class fields final) you could have an issue.
Take the following example:
public class HashMapTest {
static class KeyCheck {
int value;
public KeyCheck(int value) { this.value = value; }
public void setValue(int value) { this.value = value; }
#Override public int hashCode() { return value; }
#Override public boolean equals(Object o) {
return ((KeyCheck)o).value == this.value;
}
}
public static void main(String args[]) {
HashMap<KeyCheck, String> map = new HashMap<KeyCheck, String>();
KeyCheck k1 = new KeyCheck(5);
KeyCheck k2 = new KeyCheck(5);
map.put(k1, "Success");
System.out.println("Key: " + k1 + " Get: " + map.get(k1) +
" Contains: " + map.containsKey(k1));
System.out.println("Key: " + k2 + " Get: " + map.get(k2) +
" Contains: " + map.containsKey(k2));
k1.setValue(10);
System.out.println("Key: " + k1 + " Get: " + map.get(k1) +
" Contains: " + map.containsKey(k1));
System.out.println("Key: " + k2 + " Get: " + map.get(k2) +
" Contains: " + map.containsKey(k2));
}
}
This will print out:
Key: HashMapTest$KeyCheck#5 Get: Success Contains: true
Key: HashMapTest$KeyCheck#5 Get: Success Contains: true
Key: HashMapTest$KeyCheck#a Get: null Contains: false
Key: HashMapTest$KeyCheck#5 Get: null Contains: false
As you can see, in this case the mutability caused the hashCode() to change, which ruined everything.
Both get() and containsKey() are using the Location class's hashCode() method. The equals() method isn't called unless there is a hash collision. (thus, HashMap's get() won't use equals() in every situation.)
For your Location class, did you by chance happen to implement your own version of hashCode()? The hashCode() method should be implemented carefully. Joshua Bloch described all the details in the book Effective Java, portions of which are online... I'll go find the link to those sample chapters: Effective Java Sample Chapters. You want chapter 3.
As I asked in the comment to the question, Where does your _levels variable come from? I don't see it declared inside that method and your naming (underscore prefix, are you importing that convention from some other language?) suggests that it "lives" outside this method. Perhaps other code is changing it during execution? Please let us know when you solve it; the suspense is killing me.
i think sometime you need the hash code and sometimes not so i think in this way you can turn of the hash code checking when you want buy changing the hash code for all objects you want to 0
public class sample(){
#JsonIgnore
private int hashCode = super.hashCode();
public void setHashCode(int hashCode){
this.hashCode = hashCode;
}
#Override
public int hashCode(){
return this.hashCode;
}
#Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final ReflectObject other = (ReflectObject) obj;
if (this.hashCode != other.hashCode) {
return false;
}
return true;
}
}

Categories