我正在compareTo()
为一个简单的类实现方法(为了能够使用Collections.sort()
和Java平台提供的其他好东西):
public class Metadata implements Comparable{ private String name; private String value; // Imagine basic constructor and accessors here // Irrelevant parts omitted }
我希望这些对象的自然顺序为:1)按名称排序; 2)如果名称相同则按值排序; 两种比较都应该不区分大小写.对于这两个字段,空值完全可以接受,因此compareTo
在这些情况下不得中断.
脑海中出现的解决方案与以下几行相似(我在这里使用"保护条款",而其他人可能更喜欢单个返回点,但这不是重点):
// primarily by name, secondarily by value; null-safe; case-insensitive public int compareTo(Metadata other) { if (this.name == null && other.name != null){ return -1; } else if (this.name != null && other.name == null){ return 1; } else if (this.name != null && other.name != null) { int result = this.name.compareToIgnoreCase(other.name); if (result != 0){ return result; } } if (this.value == null) { return other.value == null ? 0 : -1; } if (other.value == null){ return 1; } return this.value.compareToIgnoreCase(other.value); }
这样做,但我对这段代码并不满意.不可否认,这不是很复杂,但是相当冗长乏味.
问题是,你如何减少冗长(保留功能)?如果有帮助,请随意参考Java标准库或Apache Commons.使(稍微)更简单的唯一选择是实现我自己的"NullSafeStringComparator",并将其应用于比较两个字段吗?
编辑1-3:Eddie是对的; 修复了上面"两个名称都为空"的情况
我在2009年就Java 1.6问了这个问题,当时Eddie的纯JDK解决方案是我首选的接受答案.直到现在(2017年)我都没有改变过这种情况.
还有第三方库解决方案 - 一个2009年的Apache Commons Collections one和一个2013 Guava one 解决方案 - 两者都是我发布的 - 我在某个时间点确实更喜欢它.
我现在由Lukasz Wiktor制作了干净的Java 8解决方案.如果在Java 8上,这绝对应该是首选,而且现在几乎所有项目都可以使用Java 8.
你可以简单地使用Apache Commons Lang:
result = ObjectUtils.compare(firstComparable, secondComparable)
使用Java 8:
private static Comparator nullSafeStringComparator = Comparator
.nullsFirst(String::compareToIgnoreCase);
private static Comparator metadataComparator = Comparator
.comparing(Metadata::getName, nullSafeStringComparator)
.thenComparing(Metadata::getValue, nullSafeStringComparator);
public int compareTo(Metadata that) {
return metadataComparator.compare(this, that);
}
我会实现一个空安全比较器.可能有一个实现,但实现起来非常简单,我总是推出自己的实现.
注意:如果两个名称均为空,则上面的比较器甚至不会比较值字段.我不认为这是你想要的.
我会用以下内容实现它:
// 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)解决方案,请参阅本答案的底部.
这就是我最终的目标.事实证明我们已经有了一个用于null安全字符串比较的实用方法,所以最简单的解决方案就是利用它.(这是一个很大的代码库;容易错过这种事:)
public int compareTo(Metadata other) { int result = StringUtils.compare(this.getName(), other.getName(), true); if (result != 0) { return result; } return StringUtils.compare(this.getValue(), other.getValue(), true); }
这就是帮助器的定义方式(它是重载的,这样你也可以定义空值是第一个还是最后一个,如果你愿意的话):
public static int compare(String s1, String s2, boolean ignoreCase) { ... }
因此,这与Eddie的答案(尽管我不会将静态辅助方法称为比较器)和uzhin 的答案基本相同.
无论如何,总的来说,我会强烈支持Patrick的解决方案,因为我认为尽可能使用已建立的库是一种很好的做法.(知道并使用 Josh Bloch所说的库.)但在这种情况下,不会产生最干净,最简单的代码.
实际上,这是一种使基于Apache Commons的解决方案NullComparator
更简单的方法.将它与类中提供的不区分大小写相Comparator
结合String
:
public static final ComparatorNULL_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
不支持泛型,所以有一个未经检查的任务.)
差不多5年后,这就是我如何处理我原来的问题.如果用Java编码,我(当然)会使用Guava.(当然不是 Apache Commons.)
把它保持在某个地方,例如在"StringUtils"类中:
public static final OrderingCASE_INSENSITIVE_NULL_SAFE_ORDER = Ordering.from(String.CASE_INSENSITIVE_ORDER).nullsLast(); // or nullsFirst()
然后,在public class Metadata implements Comparable
:
@Override public int compareTo(Metadata other) { int result = CASE_INSENSITIVE_NULL_SAFE_ORDER.compare(this.name, other.name); if (result != 0) { return result; } return CASE_INSENSITIVE_NULL_SAFE_ORDER.compare(this.value, other.value); }
当然,这几乎与Apache Commons版本(都使用JDK的CASE_INSENSITIVE_ORDER)完全相同,使用的nullsLast()
是唯一的Guava特定的东西.这个版本更可取,因为Guava是Commons Collections的优先选择.(正如大家都同意的那样.)
如果你想知道Ordering
,请注意它实现Comparator
.它非常方便,特别是对于更复杂的排序需求,例如,您可以使用链接连接多个订单compound()
.阅读订购说明了解更多!
我总是建议使用Apache commons,因为它很可能比你自己编写的更好.此外,您可以做"真正的"工作,而不是重新发明.
您感兴趣的课程是Null Comparator.它允许您使空值高或低.当两个值不为空时,您还可以使用自己的比较器.
在你的情况下,你可以有一个静态成员变量进行比较,然后你的compareTo
方法只引用它.
有点像
class Metadata implements Comparable{ private String name; private String value; static NullComparator nullAndCaseInsensitveComparator = new NullComparator( new Comparator () { @Override public int compare(String o1, String o2) { // inputs can't be null return o1.compareToIgnoreCase(o2); } }); @Override public int compareTo(Metadata other) { if (other == null) { return 1; } int res = nullAndCaseInsensitveComparator.compare(name, other.name); if (res != 0) return res; return nullAndCaseInsensitveComparator.compare(value, other.value); }
}
即使你决定自己动手,也要记住这个类,因为它在订购包含null元素的列表时非常有用.
我知道它可能不是直接回答你的问题,因为你说必须支持空值.
但我只想指出,在compareTo中支持空值与在Comparable的官方javadoc中描述的compareTo契约不一致:
请注意,null不是任何类的实例,并且即使e.equals(null)返回false,e.compareTo(null)也应抛出NullPointerException.
所以我要么显式地抛出NullPointerException,要么只是在取消引用null参数时抛出它.