我正在实施 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 应该可用于几乎所有项目。

有帮助吗?

解决方案

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

其他提示

你可以简单地使用 阿帕奇通用语言:

result = ObjectUtils.compare(firstComparable, secondComparable)

我想实现一个空的安全比较。有可能是一个实现在那里,但这是如此直白地实现,我一直滚我自己。

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

我会像下面这样实现这个:

// 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静态

请参阅此答案的底部,了解使用 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);
}

这就是帮助器的定义方式(它是重载的,因此您还可以定义 null 是排在第一位还是排在最后,如果您愿意的话):

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

所以这本质上是一样的 艾迪的回答 (尽管我不会将静态辅助方法称为 比较器) 和 乌津的 也。

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

编辑(2009):Apache Commons Collections 版本

实际上,这是一种基于 Apache Commons 的解决方案 NullComparator 更简单。将其与 不区分大小写 Comparator 提供于 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);
}

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

更新(2013):番石榴版

近 5 年后,我将如何解决我最初的问题。如果用 Java 编码,我(当然)会使用 番石榴. 。(而且相当肯定的是 不是 阿帕奇共享资源。)

把这个常数放在某个地方,例如在“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<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);
}    

当然,这几乎与Apache Commons版本相同(都使用JDK的 CASE_INSENSITIVE_ORDER), 指某东西的用途 nullsLast() 是唯一特定于番石榴的东西。这个版本更可取,仅仅是因为 Guava 作为依赖项比 Commons Collections 更可取。(作为 每个人都同意.)

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

我总是建议使用 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);
}

}

即使您决定推出自己的类,也请记住此类,因为它在对包含空元素的列表进行排序时非常有用。

我知道,它可能没有直接回答你的问题,因为你说,空值都必须支持。

但我只是想指出,在支持的compareTo空是不是在官方的 JavaDoc以可比

  

请注意null不是任何类的一个实例,和e.compareTo(空)   应该抛出一个NullPointerException即使e.equals(空)的回报   假的。

所以,我要么抛出NullPointerException明确,还是让它被抛出时,空的说法被取消引用的第一次。

您可以提取方法:

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

}

您可以设计你的类是不可变的(有效的Java第二版对此有很大的部分,第15项:最大限度地减少可变性),并确保在施工,没有空是可能的(和使用的空对象图案如果需要)。然后你就可以跳过所有这些检查和安全地假定值不为空。

我一直在寻找类似的东西,这似乎有点复杂,所以我这样做。我认为这是一个比较容易理解。您可以使用它作为一个比较器或一个衬垫。对于这个问题,你会改变对与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);
    }
}

我们可以使用Java 8做对象之间的空友好比较讨论。 本来我哈瓦与2场一个男孩等级:字符串名称和整型年龄,我想先比较名称,然后年龄,如果双方都是平等的。

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

在使用Spring的情况下任何人,还有一类org.springframework.util.comparator.NullSafeComparator,这是否对你。只是装饰自己比得上像这样

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

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

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]

对使用NullSafe比较的简单方法是使用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;
    }

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

这是我的实现,我用我的排序ArrayList的。空类分拣到最后。

对于我的情况下,延伸EntityPhone和EntityAbstract我的容器是列出

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

有关,你知道数据会不会有空值(总是字符串是个好主意)和数据是真的大,你其实之前比较值还在做三个比较,的如果您知道具体情况相信这是你的情况的,可以优化一点点位。 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);
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top