如何简化在多个字段上创建null安全比较器?

时间:2019-01-08 15:49:33

标签: java java-8 comparator

我有一个简单的比较器,可以通过其日期和时间字段比较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 :(得分:5)

听起来您的Action类具有五个字段,并且您想在所有字段上创建一个复合Comparator,而且这也是null安全的。您可以这样做:

    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()返回单例比较器实例。它适用于任何可比类型;它所做的只是调用compareTo()方法。

nullsLast()包装器在委派给其包装的比较器之前只是添加了空检查。它根本不取决于要比较的实际类型。它不是单例,但是任何nulls-last-自然顺序比较器都与其他任何比较器一样好。因此,我们可以创建一个实例并重用它。我们将其包装为一种方法,以避免在每个使用地点都将其强制转换为正确的类型。

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())调用链相比,helper方法可能更复杂。但是,如果您需要一堆不同的比较器链,那么重用辅助方法的功能可能值得其复杂性。