如何简化无效的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)按名称sorting,2)如果名称相同,则按值sorting; 这两个比较应该是不区分大小写的。 对于这两个字段,空值是完全可以接受的,所以在这种情况下, compareTo不能中断。

想到的解决scheme是沿着以下线(我在这里使用“守卫子句”,而其他人可能更喜欢单一的返回点,但这是旁边的点):

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

这样做的工作,但我不完全满意这个代码。 诚然,这不是复杂,但是相当冗长和单调乏味。

问题是, 如何减less冗余 (同时保留function)? 如果有帮助,可以随意引用Java标准库或Apache Commons。 将这个(一点)简单的唯一select是实现我自己的“NullSafeStringComparator”,并将其应用于比较两个字段?

编辑1-3 :埃迪的权利; 上面固定了“两个名字都是空”的情况

关于接受的答案

我在2009年回答了这个问题,当然是在Java 1.6上,而当时Eddie提供的纯JDK解决scheme是我首选的接受答案。 到现在为止(2017年)我从来没有想过要改变这一点。

还有第三方的图书馆解决scheme – 一个是我发布的2009年的Apache Commons Collections和一个2013年的Guava one,我确实喜欢某个时间点。

我现在通过Lukasz Wiktor制定了一个简洁的Java 8解决scheme 。 如果使用Java 8,这应该是首选,现在几乎所有项目都可以使用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); } 

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

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

编辑:修正代码示例中的拼写错误。 这就是我没有先testing它!

编辑:将nullSafeStringComparator提升为静态。

使用Guava查看更新后的(2013年)解决scheme的底部。


这是我最终去的。 事实certificate,我们已经有了一个用于空string比较的实用方法,所以最简单的解决scheme就是利用它。 (这是一个很大的代码库,很容易错过这种事:)

 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的回答基本相同(尽pipe我不会称之为比较器的静态帮助器方法) ,uzhin也是如此。

无论如何,总的来说,我会强烈支持Patrick的解决scheme ,因为我认为尽可能使用已build立的库是一个好习惯。 (像Josh Bloch所说的那样知道和使用这些库 。)但是在这种情况下,不会产生最干净,最简单的代码。

编辑(2009):Apache Commons Collections版本

实际上,这里有一个方法可以使基于Apache Commons NullComparator的解决schemeNullComparator更简单。 将它与String类中提供的不区分大小写的Comparator结合使用:

 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不支持generics,所以没有任何检查任务。)

更新(2013年):番石榴版本

近5年后,我将如何处理我最初的问题。 如果用Java编码,我会(当然)使用Guava 。 (当然不是 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<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的东西。 这个版本是最好的,因为番石榴是更好的,作为一个依赖,共同collections。 ( 大家都同意 )

如果您对Ordering ,请注意它实现了Comparator 。 这对于更复杂的sorting需求非常方便,例如允许使用compound()链接几个Orderings。 阅读订购解释更多!

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

你感兴趣的类是空比较器 。 它允许你使空值变高或变低。 当两个值不为空时,也给它自己的比较器。

在你的情况你可以有一个静态成员variables进行比较,然后你的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); } 

}

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

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

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

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

所以我要么显式抛出NullPointerException,要么只是在第一次抛出null参数的时候抛出。

你可以提取方法:

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

}

你可以devise你的类是不可变的(Effective Java 2nd Ed有一个很大的部分,Item 15:最小化可变性),并确保在构造时没有空值(如果需要,使用null对象模式 )。 然后,您可以跳过所有这些检查,并安全地假定值不为空。

我正在寻找类似的东西,这似乎有点复杂,所以我这样做。 我觉得这有点容易理解。 您可以将其用作比较器或作为一个class轮。 对于这个问题,你会改为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在对象之间做一个null友好的比较。 假设我有一个有两个字段的男孩class:string名称和整数年龄,我想先比较名称,然后老化,如果两者是平等的。

 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

另一个Apache ObjectUtils的例子。 能够对其他types的对象进行sorting。

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

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

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

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

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

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