当参数字符串为null时,int compareTo()应该返回什么?

21
据说,当输入参数为空时,compareTo() 应该抛出 NullPointerException。然而,我正在实现一个需要比较类型为 String 的字段的类。这些字段不需要是必需的。我想知道,在这种情况下,
1)当输入为 null 时应该返回什么?任何非空字符串在字典序上大于或小于 null?
2)如果这被认为是不好的做法,是否有任何支持的论据?我应该强制用户使用空字符串吗?如果使用空字符串,那么这不会混淆适用和字段为空的情况吗?如果必须抛出异常,除了在手册中警告用户之外,我还能/应该怎么做?
编辑:我可能没有表达清楚,但在我正在实现的程序中,可能为空的字符串都是一个类的字段,这些字段不应该为空。换句话说,compareTo() 使用的对象不能为 null,只有它们的私有字段可以为 null。所以在这种情况下,我相信如果我正确地实现了 compareTo(),它就不会违反可传递性的要求,因为具有 null 字段的类将始终被视为相同。我是对还是误解了这一点?
谢谢大家的回答!

如何最好地实现它:https://dev59.com/VXRB5IYBdhLWcg3w4bAo - Ciro Santilli OurBigBook.com
6个回答

24

来自Comparable的javadoc:

请注意,null不是任何类的实例, 并且e.compareTo(null)应该抛出NullPointerException, 尽管e.equals(null)返回false。


16

是的,允许实例字段使用null是没有问题的 - 只需确保其排序顺序已定义。最自然的方式是将其放置在所有实际字符串之前或之后,但您可以在此处执行任何操作,只需保持一致即可。 (例如,您可以像"null"这样对null进行排序。)

以下是单个成员的示例实现:

class Example implements Comparable<Example> {

   @Nullable
   private String member;

   // TODO: getter, setter, constructor, ...

   public int compareTo(Example that) {
      if(this.member == null)
         if(that.member == null)
            return 0; //equal
         else
            return -1; // null is before other strings
       else // this.member != null
         if(that.member == null)
            return 1;  // all other strings are after null
         else
            return this.member.compareTo(that.member);
   }
}
请注意,Comparable.compareTo() 方法的规范只对 o.compareTo(null) 进行约束(应该像 - null.compareTo(o) 一样行为,即抛出 NullPointerException),但对于如何处理 null 字段没有任何说明(它根本没有提到字段,因此类可以返回任何想要的内容,只要确保反对称性、自反性和传递性即可)。

好的,问题在于它与规范不符;-) - user207421
2
@EJP:compareTo 的规范未说明如何(甚至是否)比较对象的实例字段。 - Paŭlo Ebermann
2
是的,但是在这个问题中(至少在我回答之前的编辑后),它并不是关于将一个对象与“null”进行比较,而是将一个对象与另一个对象进行比较,其中一个对象具有“null”字段。 “compareTo”规范对此没有任何说明。 - Paŭlo Ebermann
编辑前后的问题是“当输入为空时我应该返回什么?” Javadoc 提供了答案。您的建议不符合要求。 - user207421
2
@EJP Paulo在这里是正确的。原始问题询问当foo1.compareTo(foo2)发现foo2!= nullfoo2.someField == null时该怎么办。规范只规定了当foo == null时的行为。 - skelly
显示剩余2条评论

7
不抛出异常会违反compareTo方法的反对称性,这是一个坏习惯。
来自 Comparable.compareTo文档:
实现者必须确保对于所有x和y,sgn(x.compareTo(y))== -sgn(y.compareTo(x))。 (这意味着当y.compareTo(x)抛出异常时,x.compareTo(y)必须抛出异常。)
实现者还必须确保关系是传递性的:(x.compareTo(y)> 0 && y.compareTo(z)> 0)意味着x.compareTo(z)> 0。
最后,实现者必须确保x.compareTo(y)== 0意味着对于所有z,sgn(x.compareTo(z))== sgn(y.compareTo(z))。
更重要的是,使用compareTo在对象与字符串之间进行比较是一个不好的想法,原因相同:sign(obj.compareTo(str)) != -sign(str.compareTo(obj))。实现一个自定义的Comparator并在其中进行任何操作。

谢谢!然而,在我正在实现的程序中,可能为空的字符串都是类的一部分,这些字符串不应该为空。因此,在这种情况下,我认为如果我正确地实现compareTo()方法,它就不会违反传递性要求,因为具有空字段的类始终被视为相同。我是对还是理解错了? - zw324
比较两个可能具有一些实例字段设置的类的实例是完全可以的。只需确保您以可传递的方式实现它即可。例如,c1.compareTo(c2) == -c2.compareTo(c1)。 - ykaganovich
1
仅供术语参考:条件c1.compareTo(c2) == -c2.compareTo(c1)被称为反对称性,而不是传递性。传递性主要是您引用中的第二个条件(也可能是第三个条件)。 - Paŭlo Ebermann
@Paulo,谢谢,你是正确的,而且反对称性要求比我所说的要弱。它只与符号有关,而不是实际值(受传递性约束的限制):sign(c.compareTo(c2)) == -sign(c2.compareTo(c1))。我已经调整了答案;为了上下文保留了以前的评论。 - ykaganovich

4

您需要决定null是否大于或小于非null值。您可以设计compareTo以满足类的自然排序需求,因此这不是一个不好的做法。


3
因为compareTo的文档说明它应该抛出一个NullPointerException,所以您应该遵循这些指南,以使您的实现与接口文档一致。这也解决了非空字符串是字典上小于还是大于null的问题。
在如何处理此问题方面,您有几个选项。如果空和不适用不同,那么您可能需要在自己的字段类中包装字符串字段。例如,您可以创建一种名为MyField的类型,该类型可能具有一个isApplicable方法,该方法指示该字段是否适用于此情况(或类似的内容)。或者您可以重新考虑设计,并确保空字符串和N / A确实是两个不同的事物。如果是这样,您确实需要一种区分两者的方法。

0
除了Paulo Ebermann所接受的答案外,如果您需要处理升序/降序排序,可以按照以下方式操作。(我们假设在正常升序顺序中,NULL始终位于Non-NULL之前,否则为降序。)
    final boolean sortAsc = false; // Suppose this TRUE/FALSE is for ASC/DESC
    
    objects.sort(new Comparator<Example>() {

        @Override
        public int compare(Example e1, Example e2) {

            if (e1.getMember() == null && e2.getMember() == null) {
                return 0; // Both NULLs are equal
            }
            else if (e1.getMember() == null && e2.getMember() != null) {
                return sortAsc ? -1 : 1; // NULLs should precede non-NULLs in ascending order, follow in descending order
            }
            else if (e1.getMember() != null && e2.getMember() == null) {
                return sortAsc ? 1 : -1; // Non-NULLs should follow NULLs in ascending order, precede in descending order
            } else {
                // Both non-NULLs
                return sortAsc ? e1.getMember().compareTo(e2.getMember()) 
                               : e2.getMember().compareTo(e1.getMember());
            }
        }
        
    });

1
可能更容易的方法是反转现有的比较器,而不是在三个情况下使用这种区别,可以使用Comparator.reversed()或基于自然顺序的Comparator.reverseOrder。 - Paŭlo Ebermann

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接