Cecilia Cecilia - 5 months ago 23
Java Question

How to find Set difference with custom Comparator or equals method?

I want to find the difference between two

Set<T>
using a different equality metric than that used by the class
T
, for example a custom
Comparator<T>


For example, I have a class
Animal
, which usually tests equality using the species of the
Animal


public class Animal {
public String species;
public String genus;

public Animal(String species, String genus){
this.species = species;
this.genus = genus;
}

public boolean equals(Animal other){
return other.species.equals(this.species);
}
}


I have two
List<Animal>
and I'd like to find the intersection of shared genus between the two lists.

Usually, I'd convert the
List
to
Set
and use
retainAll
to find the intersection. But here, that would give the intersection of shared species, not shared genus.

I'd like to use something like the
GenusComparator
to define equality for the intersection.

public class GenusComparator implements Comparator<Animal>{

@Override
public int compare(Animal animal1, Animal animal2) {
return String.CASE_INSENSITIVE_ORDER.compare(animal1.genus, animal2.genus);
}

}


This is just a simple example to explain what I'm trying to do, not the actual classes in my application.

Two possible solutions that I have found are


  1. Wrap the class and override the equals method

  2. Use a
    TreeSet
    with a custom Comparator



Are there other ways I've missed so far, and what are the possible pros and cons of these solutions?

Answer

The easiest way to do it, is to simple use a TreeSet with the GenusComparator.

You have to convert both sets to TreeSet(GenusComparator) for the retainAll() to work correctly.

I fixed equals() and added hashCode() and toString().

public class Test {
    public static void main(String[] args) {
        Set<Animal> set1 = new HashSet<>(Arrays.asList(new Animal("Jaguar", "Panthera"),
                                                       new Animal("Margay", "Leopardus"),
                                                       new Animal("Tiger", "Panthera")));
        Set<Animal> set2 = new HashSet<>(Arrays.asList(new Animal("Bobcat", "Lynx"),
                                                       new Animal("Cougar", "Puma"),
                                                       new Animal("Leopard", "Panthera")));
        TreeSet<Animal> treeSet1 = new TreeSet<>(new GenusComparator());
        treeSet1.addAll(set1);
        TreeSet<Animal> treeSet2 = new TreeSet<>(new GenusComparator());
        treeSet2.addAll(set2);
        treeSet1.retainAll(treeSet2);
        System.out.println(treeSet1);
    }
}
class Animal {
    public String species;
    public String genus;

    public Animal(String species, String genus) {
        this.species = species;
        this.genus = genus;
    }
    @Override
    public boolean equals(Object obj) {
        return obj instanceof Animal && this.species.equals(((Animal)obj).species);
    }
    @Override
    public int hashCode() {
        return this.species.hashCode();
    }
    @Override
    public String toString() {
        return this.species + "/" + this.genus;
    }
}
class GenusComparator implements Comparator<Animal> {
    @Override
    public int compare(Animal animal1, Animal animal2) {
        return String.CASE_INSENSITIVE_ORDER.compare(animal1.genus, animal2.genus);
    }
}

Output

[Jaguar/Panthera]