具有空值的比较器

我们有一些代码根据它们的坐标之间的距离对地址列表进行sorting。 这是通过与自定义比较器的collections.sort完成的。

然而,不时有一个没有坐标的地址出现在导致NullPointerException的列表中。 我最初的想法是解决这个问题,让比较器返回0作为至less有一个坐标为空的地址的距离。 我担心这可能会导致列表中“有效”元素的腐败。

所以在比较器中返回空值数据的“0”值,还是有一个更清晰的方法来解决这个问题。

null这样处理意味着无限远。 从而:

  • comp(1234, null) == -1
  • comp(null, null) == 0
  • comp(null, 1234) == 1

有了这个,你得到一个一致的顺序。

为了扩展WilliSchönborn的回答,我来到这里说谷歌收集正是你在这里之后。

在一般情况下,您可以编写自己的Comparator来忽略空值(假设为非null,因此它可以专注于重要的逻辑),然后使用Ordering来处理空值:

 Collections.sort(addresses, Ordering.from(new AddressComparator()).nullsLast()); 

在你的情况,虽然,它是数据在地址(坐标)被用来sorting,对吧? 谷歌集合在这种情况下更加有用。 所以你可能会更喜欢一些东西:

 // Seems verbose at first glance, but you'll probably find yourself reusing // this a lot and it will pay off quickly. private static final Function<Address, Coordinates> ADDRESS_TO_COORDINATES = new Function<Address, Coordinates>() { public Coordinates apply(Address in) { return in.getCoordinates(); } }; private static final Comparator<Coordinates> COORDINATE_SORTER = .... // existing 

那么当你想sorting:

 Collections.sort(addresses, Ordering.from(COORDINATE_SORTER) .nullsLast() .onResultOf(ADDRESS_TO_COORDINATES)); 

这正是谷歌collections的力量真正开始回报的地方。

我承认这一点是,你试图做的任何事情“做好” null坐标只是在裂缝的纸张。 你真正需要做的是find并修复注入虚假null坐标的错误。

根据我的经验,NPE错误的感染经常是由以下糟糕的编码习惯造成的:

  • input参数validation不充分,
  • 使用null来避免创build空数组或集合,
  • 应该抛出exception的时候返回null ,或者
  • 当有更好的解决scheme时,使用null来表示“没有价值”。

(对“无价值”问题的更好的解决scheme通常涉及重写代码,以便您不需要代表这个和/或使用非空值,例如空string,特殊实例,保留值。总是find一个更好的解决scheme,但你经常可以。)

如果这描述了你的应用程序,你应该花时间根除代码问题,而不是想办法隐藏NPE。

我的解决scheme(可能是有用的人在这里看)是做正常的比较,空值不是0,而是最大值可能(如Integer.MAX_VALUE)。 返回0是不一致的,如果你有它们自己的值0.这里是一个正确的例子:

  public int compare(YourObject lhs, YourObject rhs) { Integer l = Integer.MAX_VALUE; Integer r = Integer.MAX_VALUE; if (lhs != null) { l = lhs.giveMeSomeMeasure(); } if (rhs != null) { r = rhs.giveMeSomeMeasure(); } return l.compareTo(r); } 

我只是想补充说,你不需要整数的最大值。 这取决于你的giveMeSomeMeasure()方法可以返回什么。 例如,如果比较天气的摄氏度,则可以将l和r分别设置为-300或+300,具体取决于要设置空对象的位置 – 列表的头部还是尾部。

你可能不想返回0,因为这意味着地址是等距的,你真的不知道。 这是一个相当经典的问题,你正在努力处理不良的input数据。 当你不知道距离的时候,我不认为它比较器的责任是试图确定地址的实际含义。 在sorting之前,我会从列表中删除这些地址。

黑客将是将他们移动到名单的底部(但那是丑陋的!)

不要把这看成是比较器的技术问题,而是再次考虑一下需求:你真正想在这里做什么,你将如何处理这个有序列表?

  • 如果您正在尝试对它们进行sorting以首先向用户显示最相关的解决scheme,那么最好将未知位置放在最后,因此请将其视为无穷大(根据其中哪些位置返回0 / -1 / 1空值)。
  • 如果你打算使用这个结果来画一些图或者做一些其他的计算,而这些计算是依靠它们的距离来确定的,那么空值可能不应该在那里(所以要么先删除它们,要么扔一个例外,如果在这一点上,实际上不应该有任何空地点的地址)。

正如你已经意识到的那样,当其中一个为空时总是返回0在这里不是一个好主意; 它确实会损害结果。 但是,你应该做什么取决于你需要什么,而不是别人通常做/需要什么。 您的程序如何处理没有位置的地址(用户将看到的地址)应该不取决于某些技术细节,比如比较器的“最佳实践”是什么。 (对我来说,问这是什么“最佳实践”,听起来像问什么是“最好的要求”)。

不,没有更干净的方法。 也许:

  • 如果两个比较对象的坐标都为空,则返回0
  • 如果其中一个对象的坐标为空,则返回-1 / 1(取决于它是第一个还是第二个参数)

但更重要的是 – 试着摆脱/填写缺失的坐标,或者更好的是:不要在地址列表中丢失坐标。

其实,不要把它们列入名单是最合乎逻辑的行为。 如果将它们放在列表中,结果将不会按距离sorting。

您可以创build另一个列表,包含缺less坐标的地址,并向需要该信息的任何人(最终用户,API用户)说明第一个列表只包含具有所需数据的地址,而第二个列表包含地址缺乏必要的信息。

我个人讨厌在我的比较器中到处处理特殊的空案例,所以我正在寻找一个更清晰的解决scheme,并最终find谷歌collections。 他们的订单是真棒。 它们支持复合比较器,在比较之前提供对空值进行sorting并允许运行某些function。 写作比较从未如此简单。 你应该试试看。

如果您使用的是Java 8,则在Comparator类中有两个新的静态方法,它们派上用场:

 public static <T> Comparator<T> nullsFirst(Comparator<? super T> comparator) public static <T> Comparator<T> nullsLast(Comparator<? super T> comparator) 

比较将是无效的,您可以select将空值放入sorting序列的位置。

下面的例子:

 List<String> monkeyBusiness = Arrays.asList("Chimp", "eat", "sleep", "", null, "banana", "throw banana peel", null, "smile", "run"); Comparator<? super String> comparator = (a, b) -> a.compareTo(b); monkeyBusiness.stream().sorted(Comparator.nullsFirst(comparator)) .forEach(x -> System.out.print("[" + x + "] ")); 

将会打印:[null] [null] []黑猩猩[banana] [吃] [run] [睡觉] [smile] [扔香蕉皮]