Как упростить создание нулевого безопасного компаратора на нескольких полях? - PullRequest
0 голосов
/ 08 января 2019

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

static final Comparator<Action> COMPARATOR = comparing(Action::date, 
nullsLast(naturalOrder())).thenComparing(Action::time, 
nullsLast(naturalOrder()));

Пример результата показан ниже:

{hour: 01/01/2019, time: 15:55}
{hour: 01/01/2019, time: null}
{hour: 03/01/2019, time: 11:11}
{hour: 08/01/2019, time: 11:11}
{hour: 08/01/2019, time: null}
{hour: null, time: null}
{hour: null, time: null}

Компаратор должен включать еще три поля. Повторяя nullsLast(naturalOrder()) каждый раз, я расстраиваюсь без конца.

Как можно упростить использование компаратора без использования сторонней библиотеки?

1 Ответ

0 голосов
/ 10 января 2019

Похоже, у вашего Action класса есть пять полей, и вы хотите создать составной компаратор для всех полей, и это также нуль-безопасно. Вы могли бы сделать это:

    Comparator<Action> COMPARATOR1 =
        comparing(Action::date, nullsLast(naturalOrder()))
        .thenComparing(Action::time, nullsLast(naturalOrder()))
        .thenComparing(Action::foo, nullsLast(naturalOrder()))
        .thenComparing(Action::bar, nullsLast(naturalOrder()))
        .thenComparing(Action::baz, nullsLast(naturalOrder()));

Если повторение nullsLast(naturalOrder()) расстраивает, то можно создать несколько утилит, которые помогут. Во-первых, мы наблюдаем, что naturalOrder() возвращает одноэлементный экземпляр Comparator. Это работает для любого сопоставимого типа; все, что он делает, это вызывает метод compareTo().

Оболочка nullsLast() просто добавляет проверку на ноль перед передачей в свой упакованный компаратор. Это не зависит от типа, который он сравнивает. Это не синглтон, но любой компаратор нулевого-последнего-натурального порядка так же хорош, как и любой другой. Таким образом, мы можем создать один экземпляр и использовать его повторно. Мы заключаем его в метод, чтобы не приводить его к нужному типу на каждом сайте использования.

static final Comparator<?> NLNO = Comparator.nullsLast(Comparator.naturalOrder());

@SuppressWarnings("unchecked")
static <T extends Comparable<T>> Comparator<T> nlno() {
    return (Comparator<T>)NLNO;
}

Это позволяет нам делать следующее:

    Comparator<Action> COMPARATOR2 =
        comparing(Action::date, nlno())
        .thenComparing(Action::time, nlno())
        .thenComparing(Action::foo, nlno())
        .thenComparing(Action::bar, nlno())
        .thenComparing(Action::baz, nlno());

Все еще слишком многословен? Мы можем написать метод, который принимает переменное число методов доступа и создает на их основе комбинированный компаратор, сократив более чем thenComposing(). Это выглядит так:

@SafeVarargs
@SuppressWarnings("varargs")
static <C extends Comparable<C>> Comparator<Action>
comparatorWith(Function<Action, ? extends C>... extractors) {
    return Arrays.stream(extractors)
                 .map(ex -> comparing(ex, nullsLast(naturalOrder())))
                 .reduce(Comparator::thenComparing)
                 .orElseThrow(() -> new IllegalArgumentException("need at least one extractor"));
}

Это позволяет нам написать следующее:

    Comparator<Action> COMPARATOR3 =
        comparatorWith(Action::date, Action::time, Action::foo, Action::bar, Action::baz);

Стоит ли это того? Вспомогательный метод, вероятно, более сложен, чем запись одной цепочки nullsLast(naturalOrder()) вызовов. Однако если вам нужна куча различных цепочек компараторов, то возможность повторного использования вспомогательного метода может стоить его сложности.

...