I am currently trying to get a List with unique values.
Technically, it should be really simple, and the obvious choice would be a HashSet. However, I want the properties of my "points" to be the uniqueness criteria and not their "IDs".
After that, I wanted to use the stream().distinct() method. Hoping that that one is using the overridden equals method. Sadly, this won't work either.
Any solution/Idea that works for double[] is welcome as well.
PointType Class
public class PointType{
private double x;
private double y;
public PointType(x,y){
this.x = x;
this.y = y;
}
@Override
public boolean equals(Object other){
if(other instanceof PointType && this.x == other.x && this.y==other.y){
return true;
}
return false;
}
}
I am aware of the flaw of double == double. For a minimum sample, it is sufficient.
Now to the issue:
@Test
public void testUniquness(){
Set<PointType>setA = new HashSet<>();
Set<PointType>setB = new HashSet<>();
ArrayList<PointType> listA= new ArrayList<>();
ArrayList<PointType> listB= new ArrayList<>();
PointType p1 = new PointType(1.0,2.0);
PointType p2 = new PointType(1.0,2.0);
PointType p3 = new PointType(2.0,2.0);
PointType p4 = new PointType(2.0,2.0);
// Trying to use the unique properties of a HashSet
setA.add(p1);
setA.add(p2);
setA.add(p1);
setA.add(p2);
setB.add(p1);
setB.add(p2);
setB.add(p3);
setB.add(p4);
//Now with array lists and streams.
listA.add(p1);
listA.add(p2);
listA.add(p1);
listA.add(p2);
listA = (ArraList<PointType>) listA.stream().distinct().collect(Collectors.toList());
listB.add(p1);
listB.add(p2);
listB.add(p3);
listB.add(p4);
listB = (ArraList<PointType>) listB.stream().distinct().collect(Collectors.toList());
assertTrue(p1.equals(p2)); // Test passes
assertTrue(p3.equals(p4)); // Test passes
assertTrue(setA.size() == 2); // Test passes (obviously)
assertTrue(setB.size() == 2); // Test failes. How can I use my custom equality condition?
assertTrue(listA.size() == 2); // Test passes (obviously)
assertTrue(listb.size() == 2); // Test failes. How can I use my custom equality condition?
}
Any help is appreciated.
For sure, a for loop would solve this too. But there has to be a more elegant way.
CodePudding user response:
First issue, your implementation of equals
is not correct, this is what it should look like (auto-generated with IntelliJ) :
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PointType pointType = (PointType) o;
return Double.compare(pointType.x, x) == 0 && Double.compare(pointType.y, y) == 0;
}
Second and most important problem, as the word Hash
in HashSet
suggests, you should not only implement equals
but also hashCode
, and this is not only true for hash collections but in general (every time you implement equals, you also implement hash code):
@Override
public int hashCode() {
return Objects.hash(x, y);
}
Once you have done these two things, your test using Set<PointType> setB = new HashSet<>()
will pass.
CodePudding user response:
You always need to override equals()
together hashCode()
. This requirement is reflected in the documentation:
API Note:
It is generally necessary to override the
hashCode
method whenever this method is overridden, so as to maintain the general contract for thehashCode
method, which states that equal objects must have equal hash codes.
You can find more information on how to implement equals/hashCode
contract properly on SO, and in other sources, for instance, there's a separate item dedicated to this topic in "Effective Java" by Joshua Bloch.
That how the correct implementation might look like (for conciseness I've used Java 16 Pattern matching for instanceof):
public class PointType {
private double x;
private double y;
public PointType(double x, double y) {
this.x = x;
this.y = y;
}
@Override
public boolean equals(Object other) {
return other instanceof PointType o && x == o.x && y == o.y;
}
@Override
public int hashCode() {
return Objects.hash(x, y);
}
}