如何简化一个空安全的compareTo()实现?

时间:2022-08-30 11:50:21

I'm implementing compareTo() method for a simple class such as this (to be able to use Collections.sort() and other goodies offered by the Java platform):

我正在实现一个简单类的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
}

I want the natural ordering for these objects to be: 1) sorted by name and 2) sorted by value if name is the same; both comparisons should be case-insensitive. For both fields null values are perfectly acceptable, so compareTo must not break in these cases.

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

The solution that springs to mind is along the lines of the following (I'm using "guard clauses" here while others might prefer a single return point, but that's beside the point):

我想到的解决方案是沿着下面的路线(我在这里使用“警卫条款”,而其他人可能更喜欢单一的返回点,但这不是重点):

// 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);
}

This does the job, but I'm not perfectly happy with this code. Admittedly it isn't very complex, but is quite verbose and tedious.

这是工作,但我对这段代码不太满意。诚然,它不是很复杂,但却是冗长而乏味的。

The question is, how would you make this less verbose (while retaining the functionality)? Feel free to refer to Java standard libraries or Apache Commons if they help. Would the only option to make this (a little) simpler be to implement my own "NullSafeStringComparator", and apply it for comparing both fields?

问题是,您如何使这个更少啰嗦(同时保留功能)?如果有帮助,可以参考Java标准库或Apache Commons。唯一的选择是实现我自己的“NullSafeStringComparator”,并应用它来比较两个字段吗?

Edits 1-3: Eddie's right; fixed the "both names are null" case above

编辑1 - 3:埃迪的权利;修正上面的“两个名称都为空”的情况。

About the accepted answer

I asked this question back in 2009, on Java 1.6 of course, and at the time the pure JDK solution by Eddie was my preferred accepted answer. I never got round to changing that until now (2017).

我在2009年问过这个问题,当然是在Java 1.6上,当时我更愿意接受Eddie的纯JDK解决方案。直到现在(2017年),我才开始改变这种状况。

There are also 3rd party library solutions—a 2009 Apache Commons Collections one and a 2013 Guava one, both posted by me—that I did prefer at some point in time.

还有第三方图书馆的解决方案——2009年的Apache Commons系列和2013年的Guava one,都是由我发布的——我确实喜欢在某个时间点。

I now made the clean Java 8 solution by Lukasz Wiktor the accepted answer. That should definitely be preferred if on Java 8, and these days Java 8 should be available to nearly all projects.

我现在用Lukasz Wiktor做了一个干净的Java 8解决方案。如果是在Java 8上,那么应该是首选的,而现在几乎所有的项目都可以使用Java 8。

16 个解决方案

#1


129  

Using Java 8:

使用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);
}

#2


161  

You can simply use Apache Commons Lang:

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

result = ObjectUtils.compare(firstComparable, secondComparable)

#3


84  

I would implement a null safe comparator. There may be an implementation out there, but this is so straightforward to implement that I've always rolled my own.

我将实现一个空安全比较器。可能会有一个实现,但这是如此直接的实现,我总是自己滚。

Note: Your comparator above, if both names are null, won't even compare the value fields. I don't think this is what you want.

注意:上面的比较器,如果两个名称都为null,就不会比较值字段。我不认为这是你想要的。

I would implement this with something like the following:

我会用如下的东西来实现它:

// 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);
}

EDIT: Fixed typos in code sample. That's what I get for not testing it first!

编辑:在代码示例中固定的输入错误。这就是我不首先测试它的原因!

EDIT: Promoted nullSafeStringComparator to static.

编辑:将nullSafeStringComparator提升为静态。

#4


21  

See the bottom of this answer for updated (2013) solution using Guava.

使用Guava查看更新(2013)解决方案的底部。


This is what I ultimately went with. It turned out we already had a utility method for null-safe String comparison, so the simplest solution was to make use of that. (It's a big codebase; easy to miss this kind of thing :)

这就是我最终要做的。事实证明,我们已经有了一种实用的方法来进行空安全字符串比较,所以最简单的解决方法就是使用它。(这是一个大代码库;很容易错过这类事情:)

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);
}

This is how the helper is defined (it's overloaded so that you can also define whether nulls come first or last, if you want):

这是如何定义helper的(它是重载的,因此您还可以定义是否为null,如果您需要的话):

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

So this is essentially the same as Eddie's answer (although I wouldn't call a static helper method a comparator) and that of uzhin too.

这和Eddie的答案是一样的(虽然我不会调用静态助手方法和比较器)和uzhin。

Anyway, in general, I would have strongly favoured Patrick's solution, as I think it's a good practice to use established libraries whenever possible. (Know and use the libraries as Josh Bloch says.) But in this case that would not have yielded the cleanest, simplest code.

总之,总的来说,我将强烈支持Patrick的解决方案,因为我认为尽可能使用已建立的库是一种很好的做法。(如乔希·布洛赫所说,要知道并使用图书馆。)但在这种情况下,它不会产生最干净、最简单的代码。

Edit (2009): Apache Commons Collections version

Actually, here's a way to make the solution based on Apache Commons NullComparator simpler. Combine it with the case-insensitive Comparator provided in String class:

实际上,这里有一种方法可以使基于Apache Commons NullComparator的解决方案更简单。将它与String类中提供的不区分大小写的比较器组合在一起:

public static final Comparator<String> 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);
}

Now this is pretty elegant, I think. (Just one small issue remains: the Commons NullComparator doesn't support generics, so there's an unchecked assignment.)

我认为这是相当优雅的。(只剩下一个小问题:Commons NullComparator不支持泛型,因此有一个未检查的赋值。)

Update (2013): Guava version

Nearly 5 years later, here's how I'd tackle my original question. If coding in Java, I would (of course) be using Guava. (And quite certainly not Apache Commons.)

近5年之后,我将如何处理我最初的问题。如果用Java编写代码,我当然会使用Guava。(当然不是Apache Commons。)

Put this constant somewhere, e.g. in "StringUtils" class:

把这个常量放在某个地方,例如:在“StringUtils”类中:

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

Then, in public class Metadata implements Comparable<Metadata>:

然后,在公共类元数据中实现可比较的 <元数据> :

@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);
}    

Of course, this is nearly identical to the Apache Commons version (both use JDK's CASE_INSENSITIVE_ORDER), the use of nullsLast() being the only Guava-specific thing. This version is preferable simply because Guava is preferable, as a dependency, to Commons Collections. (As everyone agrees.)

当然,这与Apache Commons版本几乎相同(两者都使用JDK的case_inve_order),使用nullsLast()作为唯一的guava特定的东西。这个版本更可取,因为Guava作为一个依赖项,更适合于Commons collection。(每个人都同意。)

If you were wondering about Ordering, note that it implements Comparator. It's pretty handy especially for more complex sorting needs, allowing you for example to chain several Orderings using compound(). Read Ordering Explained for more!

如果您想要订购,请注意它实现了Comparator。特别是对于更复杂的排序需求来说,这是非常方便的,例如,允许您使用复合()来链接多个订单。阅读订购说明更多!

#5


13  

I always recommend using Apache commons since it will most likely be better than one you can write on your own. Plus you can then do 'real' work rather then reinventing.

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

The class you are interested in is the Null Comparator. It allows you to make nulls high or low. You also give it your own comparator to use when the two values are not null.

您感兴趣的类是空比较器。它允许你使零值高或低。当两个值不为空时,也可以使用自己的comparator。

In your case you can have a static member variable that does the comparison and then your compareTo method just references that.

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

Somthing like

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

static NullComparator nullAndCaseInsensitveComparator = new NullComparator(
        new Comparator<String>() {

            @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);
}

}

}

Even if you decide to roll your own, keep this class in mind since it is very useful when ordering lists thatcontain null elements.

即使您决定自己卷,也要记住这个类,因为它在包含空元素的列表时非常有用。

#6


7  

I know that it may be not directly answer to your question, because you said that null values have to be supported.

我知道它可能不是直接回答您的问题,因为您说过空值必须得到支持。

But I just want to note that supporting nulls in compareTo is not in line with compareTo contract described in official javadocs for Comparable:

但是我想指出的是,在compareTo中支持的nulls与官方javadocs中所描述的可比合同不一致:

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不是任何类的实例,e.compareTo(null)应该抛出NullPointerException,即使e = (null)返回false。

So I would either throw NullPointerException explicitly or just let it be thrown first time when null argument is being dereferenced.

因此,我要么显式地抛出NullPointerException,要么让它在null参数被取消时第一次抛出。

#7


4  

You can extract method:

你可以提取方法:

public int cmp(String txt, String otherTxt)
{
    if ( txt == null )
        return otjerTxt == null ? 0 : 1;

    if ( otherTxt == null )
          return 1;

    return txt.compareToIgnoreCase(otherTxt);
}

public int compareTo(Metadata other) {
   int result = cmp( name, other.name); 
   if ( result != 0 )  return result;
   return cmp( value, other.value); 

}

}

#8


3  

You could design your class to be immutable (Effective Java 2nd Ed. has a great section on this, Item 15: Minimize mutability) and make sure upon construction that no nulls are possible (and use the null object pattern if needed). Then you can skip all those checks and safely assume the values are not null.

您可以将类设计为不可变的(有效的Java 2 Ed.在这方面有一个很大的部分,项目15:尽量减少可变性),并确保在构建时不可能出现空值(如果需要,使用null对象模式)。然后您可以跳过所有这些检查,并安全地假定值不为空。

#9


2  

I was looking for something similar and this seemed a bit complicated so I did this. I think it's a little easier to understand. You can use it as a Comparator or as a one liner. For this question you would change to compareToIgnoreCase(). As is, nulls float up. You can flip the 1, -1 if you want them to sink.

我在寻找类似的东西,这看起来有点复杂,所以我做了这个。我想这更容易理解。您可以将其用作比较器或作为一个内衬。对于这个问题,您将更改为compareToIgnoreCase()。就像这样,nulls会浮起来。你可以翻转1,如果你想让它们下沉。

StringUtil.NULL_SAFE_COMPARATOR.compare(getName(), o.getName());

.

public class StringUtil {
    public static final Comparator<String> NULL_SAFE_COMPARATOR = new Comparator<String>() {

        @Override
        public int compare(final String s1, final String s2) {
            if (s1 == s2) {
                //Nulls or exact equality
                return 0;
            } else if (s1 == null) {
                //s1 null and s2 not null, so s1 less
                return -1;
            } else if (s2 == null) {
                //s2 null and s1 not null, so s1 greater
                return 1;
            } else {
                return s1.compareTo(s2);
            }
        }
    }; 

    public static void main(String args[]) {
        final ArrayList<String> list = new ArrayList<String>(Arrays.asList(new String[]{"qad", "bad", "sad", null, "had"}));
        Collections.sort(list, NULL_SAFE_COMPARATOR);

        System.out.println(list);
    }
}

#10


2  

we can use java 8 to do a null-friendly comparasion between object. supposed i hava a Boy class with 2 fields: String name and Integer age and i want to first compare names and then ages if both are equal.

我们可以使用java 8在对象之间做一个空友好的比较。假设我有一个有两个字段的男孩类:字符串名和整数年龄,如果两者相等,我想先比较名字和年龄。

static void test2() {
    List<Boy> list = new ArrayList<>();
    list.add(new Boy("Peter", null));
    list.add(new Boy("Tom", 24));
    list.add(new Boy("Peter", 20));
    list.add(new Boy("Peter", 23));
    list.add(new Boy("Peter", 18));
    list.add(new Boy(null, 19));
    list.add(new Boy(null, 12));
    list.add(new Boy(null, 24));
    list.add(new Boy("Peter", null));
    list.add(new Boy(null, 21));
    list.add(new Boy("John", 30));

    List<Boy> list2 = list.stream()
            .sorted(comparing(Boy::getName, 
                        nullsLast(naturalOrder()))
                   .thenComparing(Boy::getAge, 
                        nullsLast(naturalOrder())))
            .collect(toList());
    list2.stream().forEach(System.out::println);

}

private static class Boy {
    private String name;
    private Integer age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public Boy(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String toString() {
        return "name: " + name + " age: " + age;
    }
}

and the result:

结果:

    name: John age: 30
    name: Peter age: 18
    name: Peter age: 20
    name: Peter age: 23
    name: Peter age: null
    name: Peter age: null
    name: Tom age: 24
    name: null age: 12
    name: null age: 19
    name: null age: 21
    name: null age: 24

#11


1  

In case anyone using Spring, there is a class org.springframework.util.comparator.NullSafeComparator that does this for you as well. Just decorate your own comparable with it like this

在使用Spring的情况下,有一个类org.springframework.util.comparator.NullSafeComparator,它也为您提供了这个功能。就像这样装饰你自己的东西。

new NullSafeComparator<YourObject>(new YourComparable(), true)

新NullSafeComparator < YourObject >(新YourComparable(),真的)

https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/util/comparator/NullSafeComparator.html

https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/util/comparator/NullSafeComparator.html

#12


0  

Another Apache ObjectUtils example. Able to sort other types of objects.

另一个Apache ObjectUtils例子。能够对其他类型的对象进行排序。

@Override
public int compare(Object o1, Object o2) {
    String s1 = ObjectUtils.toString(o1);
    String s2 = ObjectUtils.toString(o2);
    return s1.toLowerCase().compareTo(s2.toLowerCase());
}

#13


0  

Using Java 7:

使用Java 7:

public int compareNullFirst(String str1, String str2) {
    if (Objects.equals(str1, str2)) {
        return 0;
    }
    else {
        return str1 == null ? -1 : (str2 == null ? 1 : str1.compareToIgnoreCase(str2));
    }
}

#14


0  

This is my implementation that I use to sort my ArrayList. the null classes are sorted to the last.

这是我用来排序ArrayList的实现。空类被排序到最后。

for my case, EntityPhone extends EntityAbstract and my container is List < EntityAbstract>.

对于我的情况,EntityPhone扩展EntityAbstract,我的容器是List < EntityAbstract>。

the "compareIfNull()" method is used for null safe sorting. The other methods are for completeness, showing how compareIfNull can be used.

“compareIfNull()”方法用于null安全排序。其他方法是完整的,说明如何使用compareIfNull。

@Nullable
private static Integer compareIfNull(EntityPhone ep1, EntityPhone ep2) {

    if (ep1 == null || ep2 == null) {
        if (ep1 == ep2) {
            return 0;
        }
        return ep1 == null ? -1 : 1;
    }
    return null;
}

private static final Comparator<EntityAbstract> AbsComparatorByName = = new Comparator<EntityAbstract>() {
    @Override
    public int compare(EntityAbstract ea1, EntityAbstract ea2) {

    //sort type Phone first.
    EntityPhone ep1 = getEntityPhone(ea1);
    EntityPhone ep2 = getEntityPhone(ea2);

    //null compare
    Integer x = compareIfNull(ep1, ep2);
    if (x != null) return x;

    String name1 = ep1.getName().toUpperCase();
    String name2 = ep2.getName().toUpperCase();

    return name1.compareTo(name2);
}
}


private static EntityPhone getEntityPhone(EntityAbstract ea) { 
    return (ea != null && ea.getClass() == EntityPhone.class) ?
            (EntityPhone) ea : null;
}

#15


0  

For the specific case where you know the data will not have nulls (always a good idea for strings) and the data is really large, you are still doing three comparisons before actually comparing the values, if you know for sure this is your case, you can optimize a tad bit. YMMV as readable code trumps minor optimization:

具体情况你知道数据没有null(字符串)总是一个好主意,数据非常大,你还是做前三比较实际比较值,如果你确定你是这种情况,您可以优化有点。YMMV作为可读的代码胜过次要的优化:

        if(o1.name != null && o2.name != null){
            return o1.name.compareToIgnoreCase(o2.name);
        }
        // at least one is null
        return (o1.name == o2.name) ? 0 : (o1.name != null ? 1 : -1);

#16


0  

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Comparator;

public class TestClass {

    public static void main(String[] args) {

        Student s1 = new Student("1","Nikhil");
        Student s2 = new Student("1","*");
        Student s3 = new Student("1",null);
        Student s11 = new Student("2","Nikhil");
        Student s12 = new Student("2","*");
        Student s13 = new Student("2",null);
        List<Student> list = new ArrayList<Student>();
        list.add(s1);
        list.add(s2);
        list.add(s3);
        list.add(s11);
        list.add(s12);
        list.add(s13);

        list.sort(Comparator.comparing(Student::getName,Comparator.nullsLast(Comparator.naturalOrder())));

        for (Iterator iterator = list.iterator(); iterator.hasNext();) {
            Student student = (Student) iterator.next();
            System.out.println(student);
        }


    }

}

output is

输出是

Student [name=*, id=1]
Student [name=*, id=2]
Student [name=Nikhil, id=1]
Student [name=Nikhil, id=2]
Student [name=null, id=1]
Student [name=null, id=2]

#1


129  

Using Java 8:

使用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);
}

#2


161  

You can simply use Apache Commons Lang:

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

result = ObjectUtils.compare(firstComparable, secondComparable)

#3


84  

I would implement a null safe comparator. There may be an implementation out there, but this is so straightforward to implement that I've always rolled my own.

我将实现一个空安全比较器。可能会有一个实现,但这是如此直接的实现,我总是自己滚。

Note: Your comparator above, if both names are null, won't even compare the value fields. I don't think this is what you want.

注意:上面的比较器,如果两个名称都为null,就不会比较值字段。我不认为这是你想要的。

I would implement this with something like the following:

我会用如下的东西来实现它:

// 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);
}

EDIT: Fixed typos in code sample. That's what I get for not testing it first!

编辑:在代码示例中固定的输入错误。这就是我不首先测试它的原因!

EDIT: Promoted nullSafeStringComparator to static.

编辑:将nullSafeStringComparator提升为静态。

#4


21  

See the bottom of this answer for updated (2013) solution using Guava.

使用Guava查看更新(2013)解决方案的底部。


This is what I ultimately went with. It turned out we already had a utility method for null-safe String comparison, so the simplest solution was to make use of that. (It's a big codebase; easy to miss this kind of thing :)

这就是我最终要做的。事实证明,我们已经有了一种实用的方法来进行空安全字符串比较,所以最简单的解决方法就是使用它。(这是一个大代码库;很容易错过这类事情:)

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);
}

This is how the helper is defined (it's overloaded so that you can also define whether nulls come first or last, if you want):

这是如何定义helper的(它是重载的,因此您还可以定义是否为null,如果您需要的话):

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

So this is essentially the same as Eddie's answer (although I wouldn't call a static helper method a comparator) and that of uzhin too.

这和Eddie的答案是一样的(虽然我不会调用静态助手方法和比较器)和uzhin。

Anyway, in general, I would have strongly favoured Patrick's solution, as I think it's a good practice to use established libraries whenever possible. (Know and use the libraries as Josh Bloch says.) But in this case that would not have yielded the cleanest, simplest code.

总之,总的来说,我将强烈支持Patrick的解决方案,因为我认为尽可能使用已建立的库是一种很好的做法。(如乔希·布洛赫所说,要知道并使用图书馆。)但在这种情况下,它不会产生最干净、最简单的代码。

Edit (2009): Apache Commons Collections version

Actually, here's a way to make the solution based on Apache Commons NullComparator simpler. Combine it with the case-insensitive Comparator provided in String class:

实际上,这里有一种方法可以使基于Apache Commons NullComparator的解决方案更简单。将它与String类中提供的不区分大小写的比较器组合在一起:

public static final Comparator<String> 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);
}

Now this is pretty elegant, I think. (Just one small issue remains: the Commons NullComparator doesn't support generics, so there's an unchecked assignment.)

我认为这是相当优雅的。(只剩下一个小问题:Commons NullComparator不支持泛型,因此有一个未检查的赋值。)

Update (2013): Guava version

Nearly 5 years later, here's how I'd tackle my original question. If coding in Java, I would (of course) be using Guava. (And quite certainly not Apache Commons.)

近5年之后,我将如何处理我最初的问题。如果用Java编写代码,我当然会使用Guava。(当然不是Apache Commons。)

Put this constant somewhere, e.g. in "StringUtils" class:

把这个常量放在某个地方,例如:在“StringUtils”类中:

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

Then, in public class Metadata implements Comparable<Metadata>:

然后,在公共类元数据中实现可比较的 <元数据> :

@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);
}    

Of course, this is nearly identical to the Apache Commons version (both use JDK's CASE_INSENSITIVE_ORDER), the use of nullsLast() being the only Guava-specific thing. This version is preferable simply because Guava is preferable, as a dependency, to Commons Collections. (As everyone agrees.)

当然,这与Apache Commons版本几乎相同(两者都使用JDK的case_inve_order),使用nullsLast()作为唯一的guava特定的东西。这个版本更可取,因为Guava作为一个依赖项,更适合于Commons collection。(每个人都同意。)

If you were wondering about Ordering, note that it implements Comparator. It's pretty handy especially for more complex sorting needs, allowing you for example to chain several Orderings using compound(). Read Ordering Explained for more!

如果您想要订购,请注意它实现了Comparator。特别是对于更复杂的排序需求来说,这是非常方便的,例如,允许您使用复合()来链接多个订单。阅读订购说明更多!

#5


13  

I always recommend using Apache commons since it will most likely be better than one you can write on your own. Plus you can then do 'real' work rather then reinventing.

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

The class you are interested in is the Null Comparator. It allows you to make nulls high or low. You also give it your own comparator to use when the two values are not null.

您感兴趣的类是空比较器。它允许你使零值高或低。当两个值不为空时,也可以使用自己的comparator。

In your case you can have a static member variable that does the comparison and then your compareTo method just references that.

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

Somthing like

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

static NullComparator nullAndCaseInsensitveComparator = new NullComparator(
        new Comparator<String>() {

            @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);
}

}

}

Even if you decide to roll your own, keep this class in mind since it is very useful when ordering lists thatcontain null elements.

即使您决定自己卷,也要记住这个类,因为它在包含空元素的列表时非常有用。

#6


7  

I know that it may be not directly answer to your question, because you said that null values have to be supported.

我知道它可能不是直接回答您的问题,因为您说过空值必须得到支持。

But I just want to note that supporting nulls in compareTo is not in line with compareTo contract described in official javadocs for Comparable:

但是我想指出的是,在compareTo中支持的nulls与官方javadocs中所描述的可比合同不一致:

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不是任何类的实例,e.compareTo(null)应该抛出NullPointerException,即使e = (null)返回false。

So I would either throw NullPointerException explicitly or just let it be thrown first time when null argument is being dereferenced.

因此,我要么显式地抛出NullPointerException,要么让它在null参数被取消时第一次抛出。

#7


4  

You can extract method:

你可以提取方法:

public int cmp(String txt, String otherTxt)
{
    if ( txt == null )
        return otjerTxt == null ? 0 : 1;

    if ( otherTxt == null )
          return 1;

    return txt.compareToIgnoreCase(otherTxt);
}

public int compareTo(Metadata other) {
   int result = cmp( name, other.name); 
   if ( result != 0 )  return result;
   return cmp( value, other.value); 

}

}

#8


3  

You could design your class to be immutable (Effective Java 2nd Ed. has a great section on this, Item 15: Minimize mutability) and make sure upon construction that no nulls are possible (and use the null object pattern if needed). Then you can skip all those checks and safely assume the values are not null.

您可以将类设计为不可变的(有效的Java 2 Ed.在这方面有一个很大的部分,项目15:尽量减少可变性),并确保在构建时不可能出现空值(如果需要,使用null对象模式)。然后您可以跳过所有这些检查,并安全地假定值不为空。

#9


2  

I was looking for something similar and this seemed a bit complicated so I did this. I think it's a little easier to understand. You can use it as a Comparator or as a one liner. For this question you would change to compareToIgnoreCase(). As is, nulls float up. You can flip the 1, -1 if you want them to sink.

我在寻找类似的东西,这看起来有点复杂,所以我做了这个。我想这更容易理解。您可以将其用作比较器或作为一个内衬。对于这个问题,您将更改为compareToIgnoreCase()。就像这样,nulls会浮起来。你可以翻转1,如果你想让它们下沉。

StringUtil.NULL_SAFE_COMPARATOR.compare(getName(), o.getName());

.

public class StringUtil {
    public static final Comparator<String> NULL_SAFE_COMPARATOR = new Comparator<String>() {

        @Override
        public int compare(final String s1, final String s2) {
            if (s1 == s2) {
                //Nulls or exact equality
                return 0;
            } else if (s1 == null) {
                //s1 null and s2 not null, so s1 less
                return -1;
            } else if (s2 == null) {
                //s2 null and s1 not null, so s1 greater
                return 1;
            } else {
                return s1.compareTo(s2);
            }
        }
    }; 

    public static void main(String args[]) {
        final ArrayList<String> list = new ArrayList<String>(Arrays.asList(new String[]{"qad", "bad", "sad", null, "had"}));
        Collections.sort(list, NULL_SAFE_COMPARATOR);

        System.out.println(list);
    }
}

#10


2  

we can use java 8 to do a null-friendly comparasion between object. supposed i hava a Boy class with 2 fields: String name and Integer age and i want to first compare names and then ages if both are equal.

我们可以使用java 8在对象之间做一个空友好的比较。假设我有一个有两个字段的男孩类:字符串名和整数年龄,如果两者相等,我想先比较名字和年龄。

static void test2() {
    List<Boy> list = new ArrayList<>();
    list.add(new Boy("Peter", null));
    list.add(new Boy("Tom", 24));
    list.add(new Boy("Peter", 20));
    list.add(new Boy("Peter", 23));
    list.add(new Boy("Peter", 18));
    list.add(new Boy(null, 19));
    list.add(new Boy(null, 12));
    list.add(new Boy(null, 24));
    list.add(new Boy("Peter", null));
    list.add(new Boy(null, 21));
    list.add(new Boy("John", 30));

    List<Boy> list2 = list.stream()
            .sorted(comparing(Boy::getName, 
                        nullsLast(naturalOrder()))
                   .thenComparing(Boy::getAge, 
                        nullsLast(naturalOrder())))
            .collect(toList());
    list2.stream().forEach(System.out::println);

}

private static class Boy {
    private String name;
    private Integer age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public Boy(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String toString() {
        return "name: " + name + " age: " + age;
    }
}

and the result:

结果:

    name: John age: 30
    name: Peter age: 18
    name: Peter age: 20
    name: Peter age: 23
    name: Peter age: null
    name: Peter age: null
    name: Tom age: 24
    name: null age: 12
    name: null age: 19
    name: null age: 21
    name: null age: 24

#11


1  

In case anyone using Spring, there is a class org.springframework.util.comparator.NullSafeComparator that does this for you as well. Just decorate your own comparable with it like this

在使用Spring的情况下,有一个类org.springframework.util.comparator.NullSafeComparator,它也为您提供了这个功能。就像这样装饰你自己的东西。

new NullSafeComparator<YourObject>(new YourComparable(), true)

新NullSafeComparator < YourObject >(新YourComparable(),真的)

https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/util/comparator/NullSafeComparator.html

https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/util/comparator/NullSafeComparator.html

#12


0  

Another Apache ObjectUtils example. Able to sort other types of objects.

另一个Apache ObjectUtils例子。能够对其他类型的对象进行排序。

@Override
public int compare(Object o1, Object o2) {
    String s1 = ObjectUtils.toString(o1);
    String s2 = ObjectUtils.toString(o2);
    return s1.toLowerCase().compareTo(s2.toLowerCase());
}

#13


0  

Using Java 7:

使用Java 7:

public int compareNullFirst(String str1, String str2) {
    if (Objects.equals(str1, str2)) {
        return 0;
    }
    else {
        return str1 == null ? -1 : (str2 == null ? 1 : str1.compareToIgnoreCase(str2));
    }
}

#14


0  

This is my implementation that I use to sort my ArrayList. the null classes are sorted to the last.

这是我用来排序ArrayList的实现。空类被排序到最后。

for my case, EntityPhone extends EntityAbstract and my container is List < EntityAbstract>.

对于我的情况,EntityPhone扩展EntityAbstract,我的容器是List < EntityAbstract>。

the "compareIfNull()" method is used for null safe sorting. The other methods are for completeness, showing how compareIfNull can be used.

“compareIfNull()”方法用于null安全排序。其他方法是完整的,说明如何使用compareIfNull。

@Nullable
private static Integer compareIfNull(EntityPhone ep1, EntityPhone ep2) {

    if (ep1 == null || ep2 == null) {
        if (ep1 == ep2) {
            return 0;
        }
        return ep1 == null ? -1 : 1;
    }
    return null;
}

private static final Comparator<EntityAbstract> AbsComparatorByName = = new Comparator<EntityAbstract>() {
    @Override
    public int compare(EntityAbstract ea1, EntityAbstract ea2) {

    //sort type Phone first.
    EntityPhone ep1 = getEntityPhone(ea1);
    EntityPhone ep2 = getEntityPhone(ea2);

    //null compare
    Integer x = compareIfNull(ep1, ep2);
    if (x != null) return x;

    String name1 = ep1.getName().toUpperCase();
    String name2 = ep2.getName().toUpperCase();

    return name1.compareTo(name2);
}
}


private static EntityPhone getEntityPhone(EntityAbstract ea) { 
    return (ea != null && ea.getClass() == EntityPhone.class) ?
            (EntityPhone) ea : null;
}

#15


0  

For the specific case where you know the data will not have nulls (always a good idea for strings) and the data is really large, you are still doing three comparisons before actually comparing the values, if you know for sure this is your case, you can optimize a tad bit. YMMV as readable code trumps minor optimization:

具体情况你知道数据没有null(字符串)总是一个好主意,数据非常大,你还是做前三比较实际比较值,如果你确定你是这种情况,您可以优化有点。YMMV作为可读的代码胜过次要的优化:

        if(o1.name != null && o2.name != null){
            return o1.name.compareToIgnoreCase(o2.name);
        }
        // at least one is null
        return (o1.name == o2.name) ? 0 : (o1.name != null ? 1 : -1);

#16


0  

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Comparator;

public class TestClass {

    public static void main(String[] args) {

        Student s1 = new Student("1","Nikhil");
        Student s2 = new Student("1","*");
        Student s3 = new Student("1",null);
        Student s11 = new Student("2","Nikhil");
        Student s12 = new Student("2","*");
        Student s13 = new Student("2",null);
        List<Student> list = new ArrayList<Student>();
        list.add(s1);
        list.add(s2);
        list.add(s3);
        list.add(s11);
        list.add(s12);
        list.add(s13);

        list.sort(Comparator.comparing(Student::getName,Comparator.nullsLast(Comparator.naturalOrder())));

        for (Iterator iterator = list.iterator(); iterator.hasNext();) {
            Student student = (Student) iterator.next();
            System.out.println(student);
        }


    }

}

output is

输出是

Student [name=*, id=1]
Student [name=*, id=2]
Student [name=Nikhil, id=1]
Student [name=Nikhil, id=2]
Student [name=null, id=1]
Student [name=null, id=2]