当前位置:  开发笔记 > 编程语言 > 正文

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

如何解决《如何简化null安全的compareTo()实现?》经验,为你挑选了6个好方法。

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

public class Metadata implements Comparable {
    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问了这个问题,当时Eddie的纯JDK解决方案是我首选的接受答案.直到现在(2017年)我都没有改变过这种情况.

还有第三方库解决方案 - 一个2009年的Apache Commons Collections one和一个2013 Guava one 解决方案 - 两者都是我发布的 - 我在某个时间点确实更喜欢它.

我现在由Lukasz Wiktor制作了干净的Java 8解决方案.如果在Java 8上,这绝对应该是首选,而且现在几乎所有项目都可以使用Java 8.



1> Dag..:

你可以简单地使用Apache Commons Lang:

result = ObjectUtils.compare(firstComparable, secondComparable)


在我看来,这应该是公认的答案.
@Jonik为什么你认为Apache Commons不应该是2013年接受的答案?
(@Kong:这样可以解决无效安全问题,但不会出现案例不敏感问题,这是原始问题的另一个方面.因此不会改变已接受的答案.)
@Jonik,您如何使用番石榴回答这个问题?你断言Apache Commons Lang(包'org.apache.commons.lang3`)是"遗留/维护不良/低质量"是假的,或者至多是毫无根据的.Commons Lang3易于理解和使用,并且它得到了积极的维护.它可能是我最常用的库(除Spring框架和Spring Security之外) - [StringUtils](https://commons.apache.org/proper/commons-lang/javadocs/api-release/org/apache/commons例如,具有null安全方法的/lang3/StringUtils.html)类使输入规范化变得微不足道.
此外,在我看来*Apache Commons*不应该是2013年接受的答案.(即使某些子项目比其他子项目更好.)[Guava可以用来实现同样的目的](http://stackoverflow.com/一个/五万六千二百八十五分之五十零万〇六百四十三); 参见`nullsFirst()`/`nullsLast()`.

2> Lukasz Wikto..:

使用Java 8:

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

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

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


我支持使用Java 8内置的东西支持Apache Commons Lang,但Java 8代码非常难看,而且仍然很冗长.我会坚持使用org.apache.commons.lang3.builder.CompareToBuilder.

3> Eddie..:

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

注意:如果两个名称均为空,则上面的比较器甚至不会比较值字段.我不认为这是你想要的.

我会用以下内容实现它:

// 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提升为静态.


很好地使用XOR
@James McMahon我不同意.Xor(^)可以简单地用不等于(!=)替换.它甚至可以编译为相同的字节代码.!= vs ^的用法只是品味和可读性的问题.所以,从你感到惊讶的事实来看,我会说它不属于这里.在尝试计算校验和时使用xor.在大多数其他情况下(像这一样)让我们坚持!=.
@phihag - 我知道它超过3年,但是......`final`关键字并不是真正必要的(Java代码已经很冗长了.)但是,它确实阻止了将参数重用为本地变量(一种可怕的编码实践)随着我们对软件的集体理解随着时间的推移变得越来越好,我们知道默认情况下应该是final/const/inmutable.所以我更倾向于在参数声明中使用`final`(尽管函数可能是微不足道的)来获得`quim -ability-by-quasi-default`.)它的可理解性/可维护性开销可以忽略不计.的东西.
通过用T替换String,很容易扩展这个答案,T声明为> ...然后我们可以安全地比较任何可空的Comparable对象
关于嵌套的"if"......我发现嵌套的if对于这种情况的可读性较低,所以我避免使用它.是的,有时会因此而进行不必要的比较.最后的参数是没有必要的,但是一个好主意.
@bvdb:如果首先完成`one == null && two == null`测试,那么使用`one == null ||可以使其他情况更具可读性 2个== null`.就此而言,我建议:`if(one == null || two == null){if(one == two)return 0; return lhs == null?-1:1; }`

4> Jonik..:

有关使用Guava的更新(2013)解决方案,请参阅本答案的底部.


这就是我最终的目标.事实证明我们已经有了一个用于null安全字符串比较的实用方法,所以最简单的解决方案就是利用它.(这是一个很大的代码库;容易错过这种事:)

public int compareTo(Metadata other) {
    int result = StringUtils.compare(this.getName(), other.getName(), true);
    if (result != 0) {
        return result;
    }
    return StringUtils.compare(this.getValue(), other.getValue(), true);
}

这就是帮助器的定义方式(它是重载的,这样你也可以定义空值是第一个还是最后一个,如果你愿意的话):

public static int compare(String s1, String s2, boolean ignoreCase) { ... }

因此,这与Eddie的答案(尽管我不会将静态辅助方法称为比较器)和uzhin 的答案基本相同.

无论如何,总的来说,我会强烈支持Patrick的解决方案,因为我认为尽可能使用已建立的库是一种很好的做法.(知道并使用 Josh Bloch所说的库.)但在这种情况下,不会产生最干净,最简单的代码.

编辑(2009):Apache Commons Collections版本

实际上,这是一种使基于Apache Commons的解决方案NullComparator更简单的方法.将它与类中提供的不区分大小写相Comparator结合String:

public static final Comparator NULL_SAFE_COMPARATOR 
    = new NullComparator(String.CASE_INSENSITIVE_ORDER);

@Override
public int compareTo(Metadata other) {
    int result = NULL_SAFE_COMPARATOR.compare(this.name, other.name);
    if (result != 0) {
        return result;
    }
    return NULL_SAFE_COMPARATOR.compare(this.value, other.value);
}

我觉得现在这很优雅.(只剩下一个小问题:Commons NullComparator不支持泛型,所以有一个未经检查的任务.)

更新(2013年):番石榴版

差不多5年后,这就是我如何处理我原来的问题.如果用Java编码,我(当然)会使用Guava.(当然不是 Apache Commons.)

把它保持在某个地方,例如在"StringUtils"类中:

public static final Ordering CASE_INSENSITIVE_NULL_SAFE_ORDER =
    Ordering.from(String.CASE_INSENSITIVE_ORDER).nullsLast(); // or nullsFirst()

然后,在public class Metadata implements Comparable:

@Override
public int compareTo(Metadata other) {
    int result = CASE_INSENSITIVE_NULL_SAFE_ORDER.compare(this.name, other.name);
    if (result != 0) {
        return result;
    }
    return CASE_INSENSITIVE_NULL_SAFE_ORDER.compare(this.value, other.value);
}    

当然,这几乎与Apache Commons版本(都使用JDK的CASE_INSENSITIVE_ORDER)完全相同,使用的nullsLast()是唯一的Guava特定的东西.这个版本更可取,因为Guava是Commons Collections的优先选择.(正如大家都同意的那样.)

如果你想知道Ordering,请注意它实现Comparator.它非常方便,特别是对于更复杂的排序需求,例如,您可以使用链接连接多个订单compound().阅读订购说明了解更多!


String.CASE_INSENSITIVE_ORDER确实使解决方案更加清晰.不错的更新.
无论如何你使用Apache Commons,有一个[`ComparatorChain`](https://commons.apache.org/collections/apidocs/org/apache/commons/collections/comparators/ComparatorChain.html),所以你不需要拥有`compareTo`方法.

5> Patrick..:

我总是建议使用Apache commons,因为它很可能比你自己编写的更好.此外,您可以做"真正的"工作,而不是重新发明.

您感兴趣的课程是Null Comparator.它允许您使空值高或低.当两个值不为空时,您还可以使用自己的比较器.

在你的情况下,你可以有一个静态成员变量进行比较,然后你的compareTo方法只引用它.

有点像

class Metadata implements Comparable {
private String name;
private String value;

static NullComparator nullAndCaseInsensitveComparator = new NullComparator(
        new Comparator() {

            @Override
            public int compare(String o1, String o2) {
                // inputs can't be null
                return o1.compareToIgnoreCase(o2);
            }

        });

@Override
public int compareTo(Metadata other) {
    if (other == null) {
        return 1;
    }
    int res = nullAndCaseInsensitveComparator.compare(name, other.name);
    if (res != 0)
        return res;

    return nullAndCaseInsensitveComparator.compare(value, other.value);
}

}

即使你决定自己动手,也要记住这个类,因为它在订购包含null元素的列表时非常有用.



6> Piotr Sobczy..:

我知道它可能不是直接回答你的问题,因为你说必须支持空值.

但我只想指出,在compareTo中支持空值与在Comparable的官方javadoc中描述的compareTo契约不一致:

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

所以我要么显式地抛出NullPointerException,要么只是在取消引用null参数时抛出它.

推荐阅读
围脖上的博博_771
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有