具有空值的比较器

时间:2010-03-08 13:35:32

标签: java comparator

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

但是,有时会在列表中出现没有坐标的地址,从而导致出现NullPointerException。我最初的想法是让比较器返回0作为地址的距离,其中至少有一个坐标为空。我担心这可能会导致列表中“有效”元素的破坏。

所以在比较器确定中为空数据返回'0'值,或者有更简洁的方法来解决这个问题。

9 个答案:

答案 0 :(得分:74)

处理它就像null意味着无限远。因此:

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

通过这种方式,您可以获得一致的订购。

答案 1 :(得分:22)

为了扩展WilliSchönborn的回答,我来到这里说google-collections正是你在这里所追求的。

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

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

但是,在您的情况下,它是用于排序的地址(坐标)中的数据,对吗?在这种情况下,google-collections甚至可以更多。所以你可能会有更多的东西:

// 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

然后当你想要排序:

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

这就是谷歌收藏的力量真正开始得到回报的地方。

答案 2 :(得分:8)

我对此的看法是,你试图“做好”null坐标的任何事情只是在克服裂缝。你真正需要做的是找到并修复注入虚假null坐标的错误。

根据我的经验,NPE错误的侵扰通常是由以下不良编码习惯引起的:

  • 输入参数验证不充分,
  • 使用null来避免创建空数组或集合,
  • 在应该抛出异常时返回null,或
  • 当有更好的解决方案时,使用null来表示“无价值”。

(对“无价值”问题的更好解决方案通常涉及重写代码,以便您不需要来表示此和/或使用非空值;例如,空字符串,一个特殊的实例,一个保留的值。你不能总是找到一个更好的解决方案,但你经常可以。)

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

答案 3 :(得分:8)

我的解决方案(对于看这里的人可能有用)是做比较法线,空值替换不是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,具体取决于你想要设置空对象的位置 - 列表的头部或尾部。

答案 4 :(得分:3)

你可能不想返回0因为这意味着地址是等距的,你真的不知道。在您尝试处理错误的输入数据时,这是一个非常典型的问题。当你 不知道 距离时,我不认为比较器有责任尝试确定地址在实际中的距离。我会在排序之前从列表中删除这些地址。

黑客将把它们移到列表的底部(但那太丑了!)

答案 5 :(得分:3)

如果您使用的是Java 8,那么Comparator类中有2个新的静态方法,它们会派上用场:

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

比较将为null安全,您可以选择将空值放在已排序序列中的位置。

以下示例:

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] [] [黑猩猩] [香蕉] [吃] [运行] [睡觉] [微笑] [扔香蕉皮]

答案 6 :(得分:1)

不,没有更清洁的方式。也许:

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

但更重要的是 - 尝试摆脱/填写缺失的坐标,或者更好的是:不要在列表中放置缺少坐标的地址。

实际上,不将它们列入列表是最合乎逻辑的行为。如果将它们放在列表中,结果实际上不会按距离排序。

您可以创建另一个列表,其中包含缺少坐标的地址,并向需要该信息的人(最终用户,API用户)明确说明第一个列表仅包含具有所需数据的地址,而第二个列表包含缺少必要信息的地址。

答案 7 :(得分:1)

不要把它看作是比较器的技术问题,最好再看一下这些要求:你真正想做的是什么,你打算用这个排序列表做什么? / p>

  • 如果您尝试对它们进行排序以首先向用户显示最相关的解决方案,最好将未知位置放在最后,因此将其视为无穷大(返回0 / -1 / 1,具体取决于哪个他们是null)。
  • 如果你打算使用这个结果来绘制一些图形或做一些其他依赖于它们的计算,它们实际上是按它们的距离排序的,那么无论如何都不应该在那里存在空位(所以要么首先删除它们,或者抛出异常,如果在那一点上实际上不应该是任何具有空位置的地址)。

正如你已经意识到的那样,当其中一个为空时总是返回0并不是一个好主意;它确实可以破坏结果。但是你应该做的事情取决于你需要什么,而不是取决于其他人通常做什么/需要什么。您的程序如何使用没有位置的地址(用户将要看到的内容)不应该依赖于某些技术细节,例如比较器的“最佳实践”。 (对我来说,问一下“最佳实践”是什么,听起来好像在问“最佳要求”是什么。)

答案 8 :(得分:1)

我个人讨厌在我的比较器中到处处理特殊的空案例,所以我正在寻找更清洁的解决方案,最后找到谷歌收藏品。他们的订购很棒。它们支持复合比较器,提供将空值排序到顶部和结尾,并允许在比较之前运行某些功能。编写比较器从未如此简单。你应该试一试。