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?
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
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:
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;
}
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;
}
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;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With