如何简化多个字段上空安全比较器的创建?
How to simplify creation of null-safe Comparator on multiple fields?
我有这个简单的比较器,它通过日期和时间字段比较 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())
让我很沮丧。
如何在不使用第 3 方库的情况下简化比较器的使用?
听起来你的 Action
class 有五个字段,你想在所有字段上创建一个复合比较器,这也是 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()
returns 是一个单例 Comparator 实例。它适用于任何 Comparable 类型;它所做的只是调用 compareTo()
方法。
nullsLast()
包装器在委托给其包装的比较器之前简单地添加空检查。它完全不依赖于它所比较的实际类型。它不是单例,但任何 nulls-last-natural-order 比较器都和其他任何比较器一样好。因此我们可以创建一个实例并重用它。我们将其包装在一个方法中,以避免在每个使用站点都必须将其强制转换为正确的类型。
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())
调用链更复杂。但是,如果您需要一堆不同的比较器链,那么重用辅助方法的能力可能值得它的复杂性。
我有这个简单的比较器,它通过日期和时间字段比较 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())
让我很沮丧。
如何在不使用第 3 方库的情况下简化比较器的使用?
听起来你的 Action
class 有五个字段,你想在所有字段上创建一个复合比较器,这也是 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()
returns 是一个单例 Comparator 实例。它适用于任何 Comparable 类型;它所做的只是调用 compareTo()
方法。
nullsLast()
包装器在委托给其包装的比较器之前简单地添加空检查。它完全不依赖于它所比较的实际类型。它不是单例,但任何 nulls-last-natural-order 比较器都和其他任何比较器一样好。因此我们可以创建一个实例并重用它。我们将其包装在一个方法中,以避免在每个使用站点都必须将其强制转换为正确的类型。
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())
调用链更复杂。但是,如果您需要一堆不同的比较器链,那么重用辅助方法的能力可能值得它的复杂性。