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

发布于 2024-07-12 05:35:00 字数 1960 浏览 4 评论 0 原文

我正在为这样的简单类实现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:埃迪是对的; 修复了上面的“两个名字都为空”的情况

关于接受的答案

我在 2009 年问过这个问题,当然是在 Java 1.6 上,当时 Eddie 的纯 JDK 解决方案 是我首选的接受答案。 直到现在(2017 年)我才抽出时间来改变这一点。

还有第三方库解决方案——2009 Apache Commons Collections 和 2013 Guava 的解决方案,均由我发布——在某个时间点我确实更喜欢。

我现在将 Lukasz Wiktor 提供的干净的 Java 8 解决方案作为可接受的答案。 如果在 Java 8 上,这绝对是首选,而且现在 Java 8 应该可用于几乎所有项目。

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):

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.

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?

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

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).

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.

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(18

妄司 2024-07-19 05:35:00

您可以简单地使用 Apache Commons Lang

result = ObjectUtils.compare(firstComparable, secondComparable)

You can simply use Apache Commons Lang:

result = ObjectUtils.compare(firstComparable, secondComparable)
我很坚强 2024-07-19 05:35:00

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

Using 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);
}
世俗缘 2024-07-19 05:35:00

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

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

我将通过以下内容来实现这一点:

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

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.

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.

娇纵 2024-07-19 05:35:00

请参阅此答案的底部,了解使用 Guava 的更新(2013)解决方案。


这就是我最终选择的。 事实证明,我们已经有了一个用于空安全字符串比较的实用方法,因此最简单的解决方案就是利用它。 (这是一个很大的代码库;很容易错过这种事情:)

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) { ... }

所以这本质上与埃迪的回答(虽然我不会将静态辅助方法调用为比较器)和也是 uzhin

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

编辑(2009):Apache Commons Collections版本

实际上,这是一种基于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);
}

我认为现在这非常优雅。 (只剩下一个小问题:Commons NullComparator 不支持泛型,因此存在未经检查的分配。)

更新(2013):Guava 版本

近 5 年后,以下是我如何处理我的原始版本问题。 如果用 Java 编码,我(当然)会使用 Guava< /a>. (当然不是 Apache Commons。)

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

public static final Ordering<String> 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 更可取。 (正如每个人都同意。)

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

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


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):

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.

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.

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:

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.)

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.)

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

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.)

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!

雨的味道风的声音 2024-07-19 05:35:00

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

您感兴趣的课程是 空比较器。 它允许您将空值调高或调低。 您还可以为其提供自己的比较器,以便在两个值不为空时使用。

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

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

即使您决定推出自己的

,也请记住此类,因为它在对包含空元素的列表进行排序时非常有用。

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.

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.

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

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.

嘿哥们儿 2024-07-19 05:35:00

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

但我只是想指出,在compareTo中支持空值并不符合官方用于比较的 javadocs

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

因此,我要么显式抛出 NullPointerException,要么在取消引用空参数时让它第一次抛出。

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:

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.

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

峩卟喜欢 2024-07-19 05:35:00

您可以提取方法:

public int cmp(String txt, String otherTxt)
{
    if ( txt == null )
        return otherTxt == 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); 

}

You can extract method:

public int cmp(String txt, String otherTxt)
{
    if ( txt == null )
        return otherTxt == 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); 

}

南薇 2024-07-19 05:35:00

您可以将您的类设计为不可变的(Effective Java 第 2 版有一个很好的章节,第 15 项:最小化可变性),并确保在构造时不可能出现空值(并使用 空对象模式(如果需要)。 然后您可以跳过所有这些检查并安全地假设这些值不为空。

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.

没有伤那来痛 2024-07-19 05:35:00
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);
        }


    }

}

输出是

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]
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]
書生途 2024-07-19 05:35:00

我一直在寻找类似的东西,这看起来有点复杂,所以我这样做了。 我觉得这样比较容易理解一些。 您可以将其用作比较器或单衬管。 对于这个问题,您将更改为compareToIgnoreCase()。 照原样,空值会向上浮动。 如果你想让它们沉下去,你可以翻转 1、-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);
    }
}

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.

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);
    }
}
滥情哥ㄟ 2024-07-19 05:35:00

如果有人使用 Spring,有一个类 org.springframework.util.comparator.NullSafeComparator 也可以为您完成此操作。 只需用它装饰你自己的比较

new NullSafeComparator(new YourComparable(), true)

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

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

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

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

不奢求什么 2024-07-19 05:35:00

我们可以使用 java 8 在对象之间进行空友好比较。
假设我有一个包含 2 个字段的 Boy 类:字符串名称和整数年龄,我想首先比较名称,然后比较年龄(如果两者相等)。

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

结果:

    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

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.

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
空宴 2024-07-19 05:35:00

对于您知道数据不会有空值(对于字符串来说总是一个好主意)并且数据非常大的特定情况,您仍然在实际比较值之前进行三个比较,如果您确定这是你的情况,你可以稍微优化一下。 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);

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:

        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);
記憶穿過時間隧道 2024-07-19 05:35:00

使用 NullSafe Comparator 的简单方法之一 就是使用Spring实现它,下面是一个简单的例子可以参考:

public int compare(Object o1, Object o2) {
        ValidationMessage m1 = (ValidationMessage) o1;
        ValidationMessage m2 = (ValidationMessage) o2;
        int c;
        if (m1.getTimestamp() == m2.getTimestamp()) {
            c = NullSafeComparator.NULLS_HIGH.compare(m1.getProperty(), m2.getProperty());
            if (c == 0) {
                c = m1.getSeverity().compareTo(m2.getSeverity());
                if (c == 0) {
                    c = m1.getMessage().compareTo(m2.getMessage());
                }
            }
        }
        else {
            c = (m1.getTimestamp() > m2.getTimestamp()) ? -1 : 1;
        }
        return c;
    }

One of the simple way of using NullSafe Comparator is to use Spring implementation of it, below is one of the simple example to refer :

public int compare(Object o1, Object o2) {
        ValidationMessage m1 = (ValidationMessage) o1;
        ValidationMessage m2 = (ValidationMessage) o2;
        int c;
        if (m1.getTimestamp() == m2.getTimestamp()) {
            c = NullSafeComparator.NULLS_HIGH.compare(m1.getProperty(), m2.getProperty());
            if (c == 0) {
                c = m1.getSeverity().compareTo(m2.getSeverity());
                if (c == 0) {
                    c = m1.getMessage().compareTo(m2.getMessage());
                }
            }
        }
        else {
            c = (m1.getTimestamp() > m2.getTimestamp()) ? -1 : 1;
        }
        return c;
    }
梦旅人picnic 2024-07-19 05:35:00

另一个 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());
}

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

@Override
public int compare(Object o1, Object o2) {
    String s1 = ObjectUtils.toString(o1);
    String s2 = ObjectUtils.toString(o2);
    return s1.toLowerCase().compareTo(s2.toLowerCase());
}
今天小雨转甜 2024-07-19 05:35:00

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

对于我的情况,EntityPhone 扩展了 EntityAbstract,我的容器是 List < 实体摘要>。

“compareIfNull()”方法用于空安全排序。 其他方法是为了完整性,展示了如何使用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;
}

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

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

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

@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;
}
云雾 2024-07-19 05:35:00

可以处理 null 方面并在自定义 compareTo 方法实现中使用的通用实用程序类可能如下所示:

/**
 * Generic utility class for null-safe comparison.
 */
public class Comparing
{
    /**
     * Compares two objects for order. Returns a negative integer, zero, or a
     * positive integer if the first object is less than, equal to, or greater
     * than the second object. Any of the objects can be null. A null value is
     * considered to be less than a non-null value.
     * 
     * @param <T>
     * @param a the first object.
     * @param b the second object.
     * @return an integer value.
     */
    public static <T extends Comparable<T>> int compareTo( T a, T b )
    {
        if ( a == b )
        {
            return 0;
        }

        return a != null ? b != null ? a.compareTo( b ) : 1 : -1;
    }
}

A generic utility class which can handle the null aspect and be used in a custom compareTo method implementation may look like this:

/**
 * Generic utility class for null-safe comparison.
 */
public class Comparing
{
    /**
     * Compares two objects for order. Returns a negative integer, zero, or a
     * positive integer if the first object is less than, equal to, or greater
     * than the second object. Any of the objects can be null. A null value is
     * considered to be less than a non-null value.
     * 
     * @param <T>
     * @param a the first object.
     * @param b the second object.
     * @return an integer value.
     */
    public static <T extends Comparable<T>> int compareTo( T a, T b )
    {
        if ( a == b )
        {
            return 0;
        }

        return a != null ? b != null ? a.compareTo( b ) : 1 : -1;
    }
}
旧伤还要旧人安 2024-07-19 05:35:00

如果你想要一个简单的黑客:

arrlist.sort((o1, o2) -> {
    if (o1.getName() == null) o1.setName("");
    if (o2.getName() == null) o2.setName("");

    return o1.getName().compareTo(o2.getName());
})

如果你想将空值放在列表的末尾,只需在上面的方法中更改它

return o2.getName().compareTo(o1.getName());

If you want a simple Hack:

arrlist.sort((o1, o2) -> {
    if (o1.getName() == null) o1.setName("");
    if (o2.getName() == null) o2.setName("");

    return o1.getName().compareTo(o2.getName());
})

if you want put nulls to end of the list just change this in above metod

return o2.getName().compareTo(o1.getName());
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文