Home > Enterprise >  Java Unique List
Java Unique List

Time:11-17

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 the hashCode 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);
    }
}
  • Related