Search code examples
javajava-streamdistincthashset

Java Unique List


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.


Solution

  • 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.