【问题标题】:Java: null safe compareTo methodJava:空安全 compareTo 方法
【发布时间】:2012-11-11 03:00:11
【问题描述】:

以前有人问过,但我没有找到一个合适的实现和解释。

public int compareTo(Object o)
{
    if (this == null || o == null)
    { 
        return 0;
    }
    Tok tmp = (Tok) o;      
    if (this.rang < tmp.rang)
    {
        return -1;
    } else if (this.rang > tmp.rang ) {
        return 1;
    } else {
        return 0;
    }
}

我阅读了两个我发现的类似问题;他们坚持实施另一种方法。我不明白为什么这不起作用。该方法获取一个额外的对象,并检查它是否为有效实例或null,如果null 则直接返回0;实现空安全compareTo 的最简单方法是什么?

对我有用的实现是:

public int compareTo(Object o)
{
    if (o == null)
    { 
        return 0;
    }
    Tok tmp = (Tok) o;      
    if (this.rang < tmp.rang)
    {
        return -1;
    } else if (this.rang > tmp.rang ) {
        return 1;
    } else {
        return 0;
    }
}

这不是最好的实现,人们应该看看这里有什么好人作为答案。对于我的特定情况,这已经足够了,因为它永远不会为空,但接收到的对象可以为空,并且初始实现状态是否为空返回 0。因此,如果给定对象为空,则返回 0。

【问题讨论】:

  • this 在 Java 中是从不 null
  • @MattBall 他的意思可能是 this.someInstance
  • 这段代码会将空对象视为与其他任何对象相同。可能不是最一致的排序方式。
  • 您不需要在 compareTo 中“创新”,也就是重新发明*。如果您使用 IDE,它可能会帮您选择要使用的变量。否则考虑使用 Guava 的比较链。但是,如果您对自己的实现感到满意,我会去掉那些其他的以获得更整洁的代码。
  • 这应该是 compareTo(Tok o),没有演员表

标签: java compare comparator


【解决方案1】:

就我个人而言,我喜欢 Guava's Ordering 进行 null 安全比较。您可以指定#nullsFirst()#nullsLast() 来避免NullPointerExceptions。

其他重要说明,主要来自 cmets:

  • this 在 Java 中是从不 null
  • 如果您要实现细粒度的compareTo(),请考虑使用Guava's ComparisonChain
  • 在实现Comparable 时,请务必指定类型参数,以便获得编译时类型安全,并且不必使用instanceof 或强制转换:

    class Tok implements Comparable<Tok> {
        // snip
    
        public int compareTo(Tok other) {
            // snip
        }
    }
    

【讨论】:

    【解决方案2】:

    作者坚持他不想从他的 托克[].
    这是一个允许使用 NULL 值排序的灵魂,并且不违反 java 合同

    为避免这种情况,您在 Tok 类中创建了一个违反 compareTo 合同的 compareTo, 你创建一个明确的 NullSafeComparator:

     /**
     * This comparator accepts null objects,
     * sorts ascending, null values are after non null values.
     */
    public static final class NullSafeComparator implements Comparator<Tok> {
        public int compare(Tok o1, Tok o2) {
            int r1 = Integer.MAX_VALUE;
            int r2 = Integer.MAX_VALUE;
            if (o1 != null) {
                r1 = o1.rang;
            }
            if (o2 != null) {
                r2 = o2.rang;
            }
            return (r1 < r2 ? -1 : (r1 == r2 ? 0 : 1));
        }
    }
    

    简化类 Tok(去掉它用来定义所有在一个单元测试类中的 static 关键字):

    public static class Tok {
        int rang;
        public Tok(int rang) {
            this.rang = rang;
        }
        public String toString() {
            return Integer.toString(rang);
        }
    }
    

    最后要展示一个单元测试:

    public void testSort() {
    
        Tok[] toks = new Tok[5];
        toks[0] = new Tok(3);
        toks[1] = new Tok(1);
        toks[2] = null;
        toks[3] = null;
        toks[4] = new Tok(2);
    
    
    
        Arrays.sort(toks, new NullSafeComparator());
    
    
    
        for (Tok tok: toks) {
            System.out.println(tok);
        }
        assertEquals(1, toks[0]);
        assertNull(toks[4]);
    }
    

    这将给出以下期望的结果:

    1
    2
    3
    null
    null
    

    【讨论】:

    • 冷静下来。 Comparators 允许为空安全; the JavaDoc explicitly says so: “与 Comparable 不同,比较器可以选择允许比较空参数,同时保持等价关系的要求。”
    【解决方案3】:

    根据documentation

    Note that null is not an instance of any class, and e.compareTo(null) should
    throw a NullPointerException even though e.equals(null) returns false.
    

    因此,如果您实现了一个 null 安全方法,它的行为将是意外的(也就是与文档不一致,并且可能与 API 的其余部分不一致)。

    【讨论】:

      【解决方案4】:

      看起来很奇怪,但并不安全。尝试将您的 Tok 添加到 TreeSet 或 TreeMap(作为键),您将获得 NullPointerException。问题是 TreeSet 的实现是基于 TreeMap 的。当您尝试添加(null)时,底层地图将尝试放置您的 null,这将导致 NPE

      【讨论】:

      • 谁说 OP 使用的是TreeSetTreeMap
      • @matt OP 在他的一个 cmets 中说,他使用 sort(),它使用 compateTo()。
      • @AlexWien ...这特别暗示 OP 使用TreeSetTreeMap
      • 重点是他使用 compareTo 的方式违反了 java 的 compareTo() 契约。
      【解决方案5】:

      我对其他答案不满意:
      您不应该在 compareTo 中检查 null。
      要求它抛出 NullPointerException,否则你会弄乱你的树并且很难找到你的 TreeMap 不工作的原因。

      一个非常值得推荐的方法:

       public int compareTo(Tok other) { 
          int thisRang = this.rang; 
          int otherRang = other.rang; 
          return (thisRang < otherRang ? -1 : (thisRang == otherRang ? 0 : 1)); 
        } 
        public int compareTo(Object other) { 
          return compareTo((Tok)other); 
        } 
      

      进一步完善类 Tok 应该是最终的! (否则你可能会遇到问题 当你从 Tok 子类化时。 (Sun 在上课日期犯了那个错误)

      final class Tok {
          int rang;
      }
      

      处理 compare 和 equals 并不总是那么容易,考虑使用 HashMap 代替 Trees (TreeMap),那么你不必实现 compareTo。 您应该实现 hashCode,您只需在其中返回 this.rang。

      最后强烈推荐它,但不是强制实现 equals()

      public boolean equals(Object obj) {
      return obj instanceof Tok
          && this.rang() == ((Tok) obj).rang;
      }
      

      【讨论】:

      • 将 Tok 对象数组放入 Arrays.sort(tok[]) 仍然会产生异常。
      • 是的,哪个例外?空指针?这很好!你的 Tok[] 中不应该有 Null 对象。确保不要将空对象添加到您的 Tok[]
      • 但这是一个问题,我知道我可以缩短我的 Tok[] 以不包含任何空索引,但这意味着我需要创建另一个数组来计算非空索引的数量,然后将此数组复制到临时数组并对其进行排序。它只是复杂我只是想把所有的空元素放在最大的位置,然后把它们放在最后。
      • 不,不,你可以创建一个没有空元素的数组的副本,或者你稍后会搜索你的错误 3 天或更长时间,最后会说 java sort、TreeMap、hashMap 有错误。 (就像工作同事曾经说过的那样;-))
      • @AlexWien 你似乎真的被困在使用某种类型的自分类集合的 OP 上,但这显然不是用例。
      【解决方案6】:

      比较两个对象可以像任何其他方法一样是空安全的,这里的问题是普通方法有两个参数,但compareTo 接收一个,另一个是对象本身。

      this 永远不能为空,这意味着您正在执行 null 对象(无实例)中的代码。在这种情况下,NullPointerException 将在调用 compareTo 时被直接抛出,使其代码无法执行。

      方法与对象一样多,因为比较可以基于可以为空的类的字段(旨在排除原始类型的上限)。因此,长话短说,您的空检查应该涵盖您在compareTo 中作为参数接收的对象和使用的字段。此外,如果您有一个包含某些逻辑的外部实例(即实用程序类),您应该检查该实例是否也为空。

      附带说明,如果任何涉及的对象是null,则返回的任何内容都必须一致并记录在案(您可以返回 -1 或 1,以在开头或结尾放置空值)。只需避免返回 0(这与 equalsnull 对象返回 true 的情况相同。

      【讨论】:

        【解决方案7】:

        返回 0 意味着 thiso 相等,如果 o 为 null,则不成立。此外,this 永远不会为空。

        当然,这取决于应用程序。您可能想要一个应该等于 null 的对象。您返回的内容取决于您,但如果您正在寻找一种通用的 null 安全方法,那么它并不理想。

        为了完全通用,我会检查 o 是否为 null,如果是,则抛出某种异常。

        【讨论】:

        • 根据 Comparable 的文档,特别是 NullPointerException。 docs.oracle.com/javase/6/docs/api/java/lang/Comparable.html
        • 我不需要异常我有一个 Tok[] 对象数组我把它给 Arrays.sort() 并且我得到 Null 异常。
        • Emmerioch 是对的,相信我们,不要进行空检查,请参阅下面的答案,但要使代码干净,删除或避免要排序的 Tok[] 中的空对象!走干净的路,否则你花很多时间寻找一个错误,你可能不会轻易找到。