Collection.contains yields wrong result - java

I've got an object which has got a couple of fields -- as you can see the hashcode and equals method is implemented just taking the id in account:
public class SpotResponse{
String id;
// bla bla other fields
public SpotResponse() {
}
public SpotResponse(#NonNull String id) {
this.id = id;
}
public String getId() {
return id;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SpotResponse that = (SpotResponse) o;
return id == that.id;
}
#Override
public int hashCode() {
return id.hashCode();
}
}
I've got a method which checks if a Collection<SpotResponse> newSpots
contains some oldSpots from a HashMap<String, SpotResponse> spots = new HashMap<>();
If I do this:
List<String> newKeys = new ArrayList<>();
for (SpotResponse response : newSpots) {
newKeys.add(response.getId());
}
for (SpotResponse oldSpot : spots.values()) {
if (newKeys.contains(oldSpot.getId())) {
continue;
}
/* blabla */
}
newKeys.contains() returns true properly, but if instead I do
newSpots.contains(oldSpot)
It always returns false. In this case, the Collection is an ArrayList (if this is of any help)

Your bug is in your equals implementation, on this line:
return id == that.id;
You're comparing two Strings (namely id and that.id) with ==, when you should use id.equals(that.id).

You have to be careful when using Contains with Primitives.
String is technically an object class wrapping a primitive of chars, but when you are comparing it, it is not comparing the literal object memory pointer, it is comparing the value at the memory pointer.
Contains is using .equals under the hood, so when overriding equals in your class you can't default back to the == comparison as that compares address and not necessarily value.
Hope that helps.

Related

get() and contains() methods do not find object in HashSet although it can be found by iterating and using equals() and the hashcode is the same

I have a program which first generates a Hashmap with all allowed instances of a particular object, called BoardState, as the keys. I then iterate over the keyset, creating copies of the BoardState objects and performing transformations on them and then looking up the transformed objects in my statemap and updating their associated values. The problem is that when I use the contains() method on this keyset (either directly or by first creating a HashSet of the keys) it will sometimes return false for my new object even though the object does exist in the map.
I know that the obvious answer here is that there's something wrong with my implementation of either equals() or hashcode() in BoardState or one of its fields, and I would be inclined to agree. In fact I have been able to narrow the problem down somewhat. BoardState includes as an instance variable a HashSet of Box objects, which I also implemented, and setting the hashcode() method of Box to return a constant resolves the issue (though this obviously is not an acceptable solution).
The thing is, that when I am getting the error I can still iterate through my keyset and find the object by comparing using equals(). If I then output the hashcode for this object and the object I am checking against I get the same result for each, so I'm at a loss as to why it is that contains() is throwing an error.
I apologise if the below code is a bit meaty, I've tried to strip out what I can and only show what's relevant to the error.
public class BoardState {
private static int size;
private static int totalTokens;
private static HashMap<Colour, Integer> colours;
private static HashSet<Token> fullTokenSet;
private int inactiveBoxes;
private HashSet<Box> boxes;
private HashSet<Token> offBoardTokens;
public BoardState(...){...}
public boolean checkRemoveBox(final Box box,
final HashMap<BoardState, Boolean> stateMap) {
BoardState checkState = copy();
checkState.remove(box, box.getBoxColours());
if (!stateMap.keySet().contains(checkState)) {
for (BoardState state : stateMap.keySet()) {
if (state.equals(checkState)){
System.out.println("state hashcode: " + Objects.hash(state));
System.out.println("checkstate hashcode: " +
Objects.hash(checkState));
}
}
throw new IllegalStateException ("State not found.");
} else {
if (!stateMap.get(checkState)) {
return false;
}
}
return true;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BoardState state = (BoardState) o;
return size == state.size &&
inactiveBoxes == state.inactiveBoxes &&
totalTokens == state.totalTokens &&
boxes.equals(state.boxes) &&
fullTokenSet.equals(state.fullTokenSet) &&
offBoardTokens.equals(state.offBoardTokens) &&
colours.equals(state.colours);
}
#Override
public int hashCode() {
return Objects.hash(inactiveBoxes, boxes, offBoardTokens);
}
}
public class Box {
private static int totalTokens;
private HashSet<Token> tokens;
Box(...) {...}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Box box = (Box) o;
return totalTokens == box.totalTokens &&
Objects.equals(tokens, box.tokens);
}
#Override
public int hashCode() {
return tokens.hashCode();
}
}
The given code gives the following output:
state hashcode: 157760
checkstate hashcode: 157760
Exception in thread "main" java.lang.IllegalStateException: State not found.
at game.BoardState.checkSplitBox(BoardState.java:306)
at game.BoardState.checkSplit(BoardState.java:284)
at game.Game.checkForP1Win(Game.java:173)
at game.Main.main(Main.java:11)
Process finished with exit code 1

Comparing An Entry In A Map With An Object

I have a Map in Java like so,
private HashMap<String, Object[][]> theMap;
Where the key is a String and the entry is going to be something along the line of,
theMap = new HashMap<>();
Object[][] theData = {
{Boolean.FALSE, "Text"}
};
theMap.put("Key1", theData);
Somewhere along the line I would like to check if an entry in the map is equivalent to another object. Currently I am doing it like this,
Object[][] tempData = {
{Boolean.FALSE, "Text"}
};
for(Object key: entries.keySet()) {
if(entries.get(key).equals(tempData)) {
entries.remove(key);
}
}
And it is not working.
I would prefer the comparison to be done with an object rather than with another map. I'm wondering what I'm doing wrong with this comparison here?
The reason you are not getting equality is that arrays inherit Object#equals() which is based on identity, not equality of contents. You could consider using java.util.Arrays.deepEquals(Object[], Object[]) to compare.
That is the answer to the immediate question. However, using a 2-dimensional array of Object to hold a boolean and a String is really bad code smell and indicates you need to encapsulate what you are putting in the array.
Identity vs Equivalence
Please make sure that you understand that by default the equals() method of Object checks on whether two object references are referring to the same object (identity), which is not what your code is checking.
Instead, your code is checking whether the two objects (the values you put on the map) are having the same value (equivalence).
Here are two articles about this topic:
What is the difference between identity and equality in OOP?
Overriding equals method in Java
In this particular problem of yours, I think the solution involves two steps:
Your tempData and theData does not seems to be an array
of elements of the same type (it does not appear to be a 2-dimensional
array either). Instead, it contains a Boolean value and then a
String value. In this case, I think you really should think
through what this thingy is and design a class for it (I am showing
an example below)
The class should override the equals() (and hashCode()) methods
so that you can use its equals() for equivalence checking.
Note also that your IDE (e.g. Eclipse) probably can generate a template for equals() and hashCode() for you.
Example: (here I assume your Boolean represents a condition, and your String represents a message)
class MyRecord {
private Boolean condition;
private String message;
public Boolean getCondition() {
return condition;
}
public void setCondition(Boolean condition) {
this.condition = condition;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((condition == null) ? 0 : condition.hashCode());
result = prime * result
+ ((message == null) ? 0 : message.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
MyRecord other = (MyRecord) obj;
if (condition == null) {
if (other.condition != null)
return false;
} else if (!condition.equals(other.condition))
return false;
if (message == null) {
if (other.message != null)
return false;
} else if (!message.equals(other.message))
return false;
return true;
}
}

how to delete object with same values from a java list

Say I have an object which has an id: String and name: String field.
If I have an array of these objects, if I use the remove method, since technically the Object pointer is different, the object I want won't get removed.
Ex:
List<ObjectA> newList = new ArrayList<>();
// Objecta1(id: a, name: a)
// Objecta2(id: b, name: b)
// Objecta3(id: b, name: b)
newList.add(objecta1);
newList.add(objecta2);
newList.remove(objecta3); --> will equal false
What is the cleanest way to have remove do this correctly?
I searched around and found that I could use removeIf and do a comparison there. I also could use an Iterator and then compare the fields myself. Just wondering if there is a canonical, clean way of doing it in Java. Thanks.
Beside the solution override equals, you can do it with java 8:
newList.removeIf(p -> p.getId().equals(objecta3.getId()));
Your object should #Override the equals method.
Add the following to your class:
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ObjectA)) return false;
ObjectA objectA = (ObjectA) o;
if (getId() != objectA.getId()) return false;
return getName() != null ? getName().equals(objectA.getName()) : objectA.getName() == null;
}
#Override
public int hashCode() {
int result = getId();
result = 31 * result + (getName() != null ? getName().hashCode() : 0);
return result;
}
and then:
while(newList.remove(objecta3)){};

Not sure how to complete this equals method

Could someone help me with this question please? I've tried looking up other examples of this to find what I need to do and keep running into something called and EqualsBuilder, is that what I need to use? Do I need to have it call on equals again if it satisfies neither of the IFs?
The following code contains a class definition and an incomplete method definition. The equals method is used to compare Buildings.
It is intended to return true if Buildings have the same names and number of floors (but are not necessarily the same Building) and false otherwise.
public class Building {
private String name;
private int noOfFloors;
public boolean equals (Object rhs) {
if (this == rhs) {
return true;
}
if (!(rhs instanceof Building)) {
return false;
}
Building b = (Building) rhs;
// missing return statement
}
}
public boolean equals (Object rhs) {
if (this == rhs) {
return true;
}
if (!(rhs instanceof Building)) {
return false;
}
Building b = (Building) rhs;
// This is what you're supposed to add. It will return true only if both
// object's attributes (name and number of floors) are the same
return this.name.equals(b.name) && this.noOfFloors == b.noOfFloors;
}
The only thing that you have to test for now is the fields of both objects. If they are equal, then you should return true, if at least one of them is not then you should return false.
Since your fields in that case are int and Stringyou can use == for the integer field and .equals() for the String field.
Something like this should do the job just fine:
if(this.name.equals(b.name) && this.noOfFloors == b.noOfFloors){
return true ;
}
else{
return false;
}
After the instanceOf tests you want to compare the fields of the object to the other object. Something like Objects.deepEquals() should do the trick for you nicely.

Comparing two collections using hamcrest contains() method

I have two collections which I am trying to compare for equality in my unit tests, but I am struggling with the contains method. Here is what I have:
#Test
public void getAllItems() {
Collection<Item> actualItems = auction.getAllItems(joe);
Collection<Item> expectedItems = Lists.newArrayList();
expectedItems.add(iPhone);
expectedItems.add(skateboard);
assertThat(expectedItems, contains(actualItems));
}
items contains the same objects as expectedItems so I would expect the assertion to be true but this is the output I get:
[Item{name=iPhone}, Item{name=Skateboard}] --> Expected
[Item{name=iPhone}, Item{name=Skateboard}] --> Actual
java.lang.AssertionError:
Expected: iterable containing [<[Item{name=iPhone}, Item{name=Skateboard}]>]
but: item 0: was <Item{name=iPhone}>
at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:8)
Please can you help me where I am going wrong with using the contains method?
public class Item {
private String name;
public Item(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String toString() {
return Objects.toStringHelper(this).add("name", name).toString();
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Item other = (Item) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
A Collection's .contains(...) uses the equals and hashCode methods of the Objects. In order to use equals (or in this case contains) on your own Objects, you need to override the equals and hashCode methods of your class. This is because Java uses references behind the scenes, so even though the field may be equal, the Object-references are not.
In Eclipse you can generate them using right-mouse click -> Source -> Generate hashCode() and equals().... But, since you never stated you use Eclipse, here is an example of the methods that are generated:
// Overriding this class' equals and hashCode methods for Object comparing purposes
// using the Collection's contains
// contains does the following behind the scenes: Check if both inputs aren't null,
// check if the HashCodes match, check if the Objects are equal.
// Therefore to use the Collection's contains for Objects with the same fields, we
// need to override the Object's equals and hashCode methods
// These methods below are generated by Eclipse itself using "Source -> Generate
// hashCode() and equals()..."
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if(this == obj)
return true;
if(obj == null)
return false;
if(getClass() != obj.getClass())
return false;
Item other = (Item) obj;
if(name == null){
if(other.name != null)
return false;
}
else if(!name.equals(other.name))
return false;
return true;
}
If you add both of these to your Item-class, the contains will work.
EDIT:
I'm not sure, but when I look at your code I think the following might be wrong:
#Test
public void getAllItems() {
Collection<Item> actualItems = auction.getAllItems(joe);
Collection<Item> expectedItems = Lists.newArrayList();
// You first print both lists
System.out.println(expectedItems);
System.out.println(items);
// And then add the two items to the expectedItems
expectedItems.add(iPhone);
expectedItems.add(skateboard);
assertThat(expectedItems, contains(actualItems));
}
If you try the following instead:
#Test
public void getAllItems() {
Collection<Item> actualItems = auction.getAllItems(joe);
Collection<Item> expectedItems = Lists.newArrayList();
// First add both items
expectedItems.add(iPhone);
expectedItems.add(skateboard);
// Then print both lists
System.out.println(expectedItems);
System.out.println(items);
assertThat(expectedItems, contains(actualItems));
}
Does the expectedList now contain 4 items?
[Item{name=iPhone}, Item{name=Skateboard}, Item{name=iPhone}, Item{name=Skateboard}] --> Expected
[Item{name=iPhone}, Item{name=Skateboard}] --> Actual
In that case you shouldn't add the two items, since they are already present in the list.
Also, you're trying to use the contains on the entire list. Normally the contains is used to see if a single item is present in the list. So you could either use something like this:
for(Item i : expectedList){
assertTrue(actualList.contains(i));
}
or perhaps something like this, in case you use these libraries:
assertThat(actualList, is(expectedList));
I'm not sure if this is the cause and if this will fix it, since you use a different JUnit library then I usually do and I'm not sure if these syntax with the Asserts are possible.
I really don't think you actually need hamcrest for this. Wouldn't it be easier to make the asserts in one of the following ways:
A list is still an object at the end of the day:
org.junit.Assert.assertEquals(expected, actual)
An old fashion functionality for lists by using containsAll(..):
org.junit.Assert.assertTrue(expectedItems.containsAll(actualItems))
Using asserts for arrays' equality:
org.junit.Assert.assertArrayEquals(expectedItems.toArray(), actualItems.toArray())
Of course you can use hamcrest as well:
org.hamcrest.MatcherAssert.assertThat(actual, Matchers.containsInAnyOrder(actual.toArray()));
OR
org.hamcrest.MatcherAssert.assertThat(actual, Matchers.contains(actual.toArray()));
You basically asserting that expectedItems is a list with one element and this element is expected to be a list itself with the two items iPhone and skateboard.
To assert that expectedItems and actualItems have the same elements in the same order try this:
#Test
public void getAllItems() {
Collection<Item> actualItems = auction.getAllItems(joe);
assertThat(actualItems, contains(iPhone, skateboard));
}
And beware that assertThat expects the "actual" object as first parameter and not the "expected".
Alternatively you can do something like that:
#Test
public void getAllItems() {
Collection<Item> actualItems = auction.getAllItems(joe);
Collection<Item> expectedItems = Lists.newArrayList();
expectedItems.add(iPhone);
expectedItems.add(skateboard);
assertThat(actualItems, contains(expectedItems.toArray(new Item[expectedItems.size()])));
}

Categories

Resources