如何简化null安全的compareTo()实现?

问题

我正在为一个简单的类实现compareTo()方法(为了能够使用Collections.sort()和Java平台提供的其他好东西):

public class Metadata implements Comparable<Metadata> {
    private String name;
    private String value;

// Imagine basic constructor and accessors here
// Irrelevant parts omitted
}

我希望这些对象的自然顺序为:1)按名称排序; 2)如果名称相同则按值排序;两种比较都应该不区分大小写。对于这两个字段,空值完全可以接受,因此在这些情况下,compareTo不能中断。

脑海中出现的解决方案与以下几行相似(我在这里使用"保护条款",而其他人可能更喜欢单个返回点,但这不是重点):

// primarily by name, secondarily by value; null-safe; case-insensitive
public int compareTo(Metadata other) {
    if (this.name == null && other.name != null){
        return -1;
    }
    else if (this.name != null && other.name == null){
        return 1;
    }
    else if (this.name != null && other.name != null) {
        int result = this.name.compareToIgnoreCase(other.name);
        if (result != 0){
            return result;
        }
    }

    if (this.value == null) {
        return other.value == null ? 0 : -1;
    }
    if (other.value == null){
        return 1;
    }

    return this.value.compareToIgnoreCase(other.value);
}

这样做,但我对这段代码并不满意。不可否认,它不是非常复杂,但是相当冗长乏味。

问题是,你会如何使这个更简洁的(同时保留功能)?如果有帮助,请随意参考Java标准库或Apache Commons。使(稍微)更简单的唯一选择是实现我自己的"NullSafeStringComparator",并将其应用于比较两个字段吗?

编辑1-3:Eddie的权利;修复了上面"两个名字都为空"的情况

##关于接受的答案

我在2009年回答了这个问题,当然是在Java 1.6上,当时the pure JDK solution by Eddie是我首选的答案。直到现在(2017年),我从未改变过这种情况。

还有3rd party library solutions-a 2009 Apache Commons Collections one和2013 Guava one,两者都由我发布 - 我在某个时间点确实更喜欢。

我现在做了cleanJava 8 solution by Lukasz Wiktor接受的答案。如果在Java 8上,这绝对应该是首选,而且现在几乎所有项目都可以使用Java 8。


#1 热门回答(165 赞)

你可以简单地使用Apache Commons Lang

result = ObjectUtils.compare(firstComparable, secondComparable)

#2 热门回答(134 赞)

使用Java 8

private static Comparator<String> nullSafeStringComparator = Comparator
        .nullsFirst(String::compareToIgnoreCase); 

private static Comparator<Metadata> metadataComparator = Comparator
        .comparing(Metadata::getName, nullSafeStringComparator)
        .thenComparing(Metadata::getValue, nullSafeStringComparator);

public int compareTo(Metadata that) {
    return metadataComparator.compare(this, that);
}

#3 热门回答(85 赞)

我会实现一个空安全比较器。可能有一个实现,但实现起来非常简单,我总是推出自己的实现。

注意:上面的比较器ifbothnames为null,甚至不会比较值字段。我不认为这是你想要的。

我会用以下内容实现:

// primarily by name, secondarily by value; null-safe; case-insensitive
public int compareTo(final Metadata other) {

    if (other == null) {
        throw new NullPointerException();
    }

    int result = nullSafeStringComparator(this.name, other.name);
    if (result != 0) {
        return result;
    }

    return nullSafeStringComparator(this.value, other.value);
}

public static int nullSafeStringComparator(final String one, final String two) {
    if (one == null ^ two == null) {
        return (one == null) ? -1 : 1;
    }

    if (one == null && two == null) {
        return 0;
    }

    return one.compareToIgnoreCase(two);
}

编辑:修复了代码示例中的拼写错误。这就是我没有先测试它的原因!

编辑:将nullSafeStringComparator提升为静态。