Универсальный метод сравнения объектов с переменным количеством ссылок на методы для сравнения

Мне часто приходится сравнивать экземпляры определенного типа на равенство, но мне нужно сравнивать не все, а только определенные поля. Я обычно так делаю:

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) {
...
}

Поскольку мне приходится делать это очень часто, мне интересно, есть ли общий способ сделать это.
Все объекты, которые мне нужно сравнить, расширяют определенный базовый класс. Есть ли способ написать статический метод, который может выполнять все эти сравнения за меня? Я думал о статическом методе, который должен иметь параметры для сравниваемых объектов и параметр vararg для всех ссылок на методы:

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

Но я не знаю, как передавать ссылки на методы и как использовать их в компараторе внутри метода. Можно ли это сделать каким-то образом или я должен попробовать другой подход?

Только ли ваш базовый класс содержит все параметры, по которым вы хотите проводить сравнения, или дочерние классы по отдельности могут также содержать некоторые другие параметры, которые следует использовать для проверок сравнения ...?

Ankur Chrungoo 04.12.2018 12:19

Дочерние классы также содержат некоторые параметры, которые необходимо сравнить.

Chris Guettar 04.12.2018 12:26
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
9
2
294
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Мне удалось придумать кое-что, что вроде бы работает:

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;
}

Тестовые классы:

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;}
}

Использование:

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));

Вывод:

false
true

Использование varargs с дженериками приведет к предупреждению компилятора, например Potential heap pollution via varargs parameter. Как правило, чтобы избежать таких вещей, мы можем предоставить несколько функций, как показано ниже:

  1. С одним полем:

    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. С двумя полями:

    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. Используя varargs. Поскольку мы ничего не сохраняем из fields, мы можем использовать @SafeVarargs по этому методу:

    @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;
    }
    

Отредактировано для передачи другого типа свойств.

Sukhpal Singh 04.12.2018 13:44

Вот основная идея, которую вы можете использовать:

List<Function<SomeType, Comparable>> fieldsToTest = new ArrayList<>();
fieldsToTest.add(SomeType::getType);
fieldsToTest.add(SomeType::getSite);
System.out.println(areFieldsEqual(new SomeType(...), new SomeType(...), fieldsToTest));

Реализация метода areFieldsEqual:

static <T extends BaseType> boolean areFieldsEqual(T left, T right, 
                                                   List<Function<T, Comparable>> fields) {
    Comparator<T> c = fields.stream().reduce(
        (l, r) -> 0, Comparator::thenComparing, Comparator::thenComparing);
    return c.compare(left, right) == 0;
}

Или, если вы хотите придерживаться параметра varargs:

static <T extends BaseType> boolean areFieldsEqual(T left, T right,
                                                   Function<T, Comparable>... fields) {
    Comparator<T> c = Arrays.stream(fields).reduce(
        (l, r) -> 0, Comparator::thenComparing, Comparator::thenComparing);
    return c.compare(left, right) == 0;
}

Другие вопросы по теме