Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic object comparison method with a variable number of method references for comparison

I often have to compare to instances of a certain type for equality, but I do not need to compare everything, but only certain fields. I usually do it like this:

Comparator<SomeType> c = Comparator.comparing(SomeType::getNumber)
  .thenComparing(SomeType::getType)
  .thenComparing(SomeType::getSite)
  .thenComparing(SomeType::getAddition)
  .thenComparing(SomeType::getImportantFlag);

if (c.compare(old, new) == 0) {
...
}

As I have to to this really often, I am wondering if there is a generic way to do this.
All the objects I have to compare extend a certain base class. Is there a way to write a static method which can do all these comparisons for me? I was thinking of a static method which would have to parameters for the objects to compare and a vararg parameter for all the method references:

public static <T extends BaseType> boolean areFieldsEqual(T left, T right, whatShouldIPutHere... fields) {
}

But I do not know how to pass the method references and how to use them in a comparator within the method. Can this be done in some way or is there a different approach I should try?

like image 493
Chris Guettar Avatar asked Dec 04 '18 11:12

Chris Guettar


Video Answer


2 Answers

I managed to come up with something that seems to work:

public static <T extends BaseType> boolean areFieldsEqual(T left, T right, Function<T,? extends Comparable>... fields)
{
    if (fields.length < 1) {
        return true;
    }
    Comparator<T> c = Comparator.comparing(fields[0]);
    for (int i = 1; i < fields.length; i++) {
        c = c.thenComparing (fields[i]);
    }
    return c.compare(left, right) == 0;
}

Test classes:

class BaseType {
    String x;
    int y;
    public BaseType (String x, int y) {
        this.x=x;
        this.y=y;
    }
    String getX () {return x;}
    int getY () { return y;}
}

class SubType extends BaseType {
    String z;

    public SubType (String x, int y,String z) {
        super(x,y);
        this.z=z;
    }
    String getZ () {return z;}
}

Usage:

BaseType one = new BaseType("some",1);
BaseType two = new BaseType("some",2);
SubType three = new SubType("some",1,"else");
SubType four = new SubType("some",2,"else");
System.out.println (areFieldsEqual(one,two,BaseType::getX,BaseType::getY));
System.out.println (areFieldsEqual(three,four,SubType::getZ,BaseType::getX));

Output:

false
true
like image 191
Eran Avatar answered Oct 07 '22 01:10

Eran


Using varargs with generics will result in compiler warning like Potential heap pollution via varargs parameter. Generally to avoid such things we can provide few functions like below:

  1. With single field:

    public static <T extends BaseType> boolean areFieldsEqual(
      T first, T second, Function<? super T, ? extends Comparable> keyExtractor) {
        Comparator<T> comp = Comparator.comparing(keyExtractor);
        return comp.compare(first, second) == 0;
    }
    
  2. With two fields:

    public static <T extends BaseType> boolean areFieldsEqual(
          T first, T second, Function<T, ? extends Comparable> firstField,
          Function<T, ? extends Comparable> secondField) {
        Comparator<T> comp = Comparator.comparing(firstField).thenComparing(secondField);
        return comp.compare(first, second) == 0;
    }
    
  3. Using varargs. As we aren't storing anything from fields we can use @SafeVarargs over this method:

    @SafeVarargs
    public static <T extends BaseType> boolean areFieldsEqual(
      T first, T second, Function<T, ? extends Comparable>... fields) {
        if (fields.length < 1) {
          return true;
        }
        Comparator<T> comp = Comparator.comparing(fields[0]);
        for (int i = 1; i < fields.length; i++) {
          comp = comp.thenComparing(fields[i]);
        }
        return comp.compare(first, second) == 0;
    }
    
like image 44
Sukhpal Singh Avatar answered Oct 06 '22 23:10

Sukhpal Singh