关于Java:如何简化null安全的compareTo()实现?

您所在的位置:网站首页 null可以equals吗 关于Java:如何简化null安全的compareTo()实现?

关于Java:如何简化null安全的compareTo()实现?

#关于Java:如何简化null安全的compareTo()实现? | 来源: 网络整理| 查看: 265

我正在为这样的简单类实现compareTo()方法(以便能够使用Collections.sort()和Java平台提供的其他功能):

1234567public class Metadata implements Comparable {     private String name;     private String value; // Imagine basic constructor and accessors here // Irrelevant parts omitted }

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

我想到的解决方案是遵循以下思路的(我在这里使用"保护条款",而其他人可能更喜欢单个返回点,但这并不重要):

123456789101112131415161718192021222324// 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应该可以用于几乎所有项目。

相关讨论 stackoverflow.com/questions/369383/

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

1result = ObjectUtils.compare(firstComparable, secondComparable) 相关讨论 我认为这应该是公认的答案。 (@Kong:这是为了确保无效性,而不是不区分大小写,这是原始问题的另一方面。因此,不更改公认的答案。) 另外,在我看来,Apache Commons在2013年不应被接受。(即使某些子项目比其他子项目得到更好的维护。)Guava可用于实现同一目标;参见nullsFirst() / nullsLast()。 @Jonik为什么您认为Apache Commons在2013年不应该被接受? Apache Commons的大部分是遗留的/维护不良的/低质量的东西。它提供的大多数功能都有更好的选择,例如,在Guava中,这是一个非常高质量的库,在JDK中也越来越多。是的,大约在2005年左右,Apache Commons很烂,但是如今,大多数项目中都不需要它了。 (当然,有例外;例如,如果出于某种原因需要FTP客户端,则ID可能在Apache Commons Net中使用该客户端,依此类推。) @Jonik,您如何用番石榴回答问题?您关于Apache Commons Lang(软件包org.apache.commons.lang3)是"旧版/维护不良/质量低下"的说法是错误的,或者充其量是毫无根据的。 Commons Lang3易于理解和使用,并且积极维护。它可能是我最常使用的库(除了Spring Framework和Spring Security)-例如,StringUtils类及其null安全方法使输入规范化变得微不足道。 我写的是" Apache Commons的大部分都是遗留的",我认为这仍然是事实。您可能会说Commons Lang 3很好并且得到了积极维护。在每种情况下,如今Java 8解决方案都是必经之路。早在2013年,我确实发布了Guava版本,但是由于这个特殊的问题,Id不再使用Guava(除非出于任何原因不能使用Java 8)。 包括一个巨大的厨房水槽库,以节省几行代码?赢得。 在流中,它看起来像这样:.sorted((o1, o2) -> ObjectUtils.compare(o1.getAttribute(), o2.getAttribute()))。 我不明白为什么这个答案有195票,因为这个答案会产生一个StackOverflow异常,因为ObjectUtils比较方法只是检查null,但是对于字段的真正比较,它调用Comparable实例的compareTo方法,即不能在此方法内部使用,否则将循环...还是此时间更改了,现在不能再执行了?

使用Java 8:

12345678910private 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); } 相关讨论 我支持使用Java 8内置的东西来支持Apache Commons Lang,但是Java 8代码非常难看,而且仍然很冗长。我将暂时使用org.apache.commons.lang3.builder.CompareToBuilder。 这不适用于Collections.sort(Arrays.asList(null,val1,null,val2,null)),因为它将尝试在null对象上调用compareTo()。坦白地说,这似乎是集合框架的问题,试图找出解决方法。

我将实现一个null安全比较器。可能有一个实现,但是实现起来如此简单,以至于我总是自己动手做。

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

我将通过以下类似的方式实现此目的:

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

相关讨论 您的代码有点拙劣:这里不需要final,String类的第一个字符用Java大写。此外,返回1:-1是无效的Java。 ID建议嵌套if(一个== null){如果两个== null)返回0;否则返回-1;}否则{if(两个== null)返回0;否则返回1;} 对。我应该先测试一下。现在经过测试。 :) 关于嵌套的" if" ...对于这种情况,我发现嵌套的if不太易读,因此避免了它。是的,因此有时会进行不必要的比较。对于参数,final不是必需的,但这是一个好主意。 还是返回一个== null? (two == null?0:-1):两个== null? -1:一个.compareToIgnoreCase(两个); @Peter Lawrey:是的,但是可读性较差。有了良好的优化程序,在优化版本和可读版本之间应该没有实际的速度差异。 很好地使用XOR @phihag-我知道它已经使用了三年多了,但是final关键字并不是真正必需的(Java代码已经很冗长了。)但是,它确实阻止了将参数重用为局部变量(这是一种糟糕的编码习惯) 。)随着我们对软件的集体理解随着时间的推移越来越好,我们知道默认情况下事物应该是final / const / inmutable。因此,我更喜欢在参数声明中使用final来获得inmutability-by-quasi-default(这可能是微不足道的)。它的可理解性/可维护性开销在整体方案中可以忽略不计。 @詹姆斯·麦克马洪我必须不同意。 Xor(^)可以简单地用不等于(!=)代替。它甚至可以编译为相同的字节码。 != vs ^的用法只是味道和可读性的问题。因此,从您感到惊讶的事实来看,我会说它不属于这里。当您尝试计算校验和时,请使用xor。在大多数其他情况下(例如这种情况),请坚持使用!=。 @bvdb:如果首先进行one==null && two==null测试,则使用one==null || two==null可以使其他情况更具可读性。为此,我建议:if (one==null || two==null) { if (one==two) return 0; return lhs==null ? -1 : 1; } 在第二个中,如果您可以只写if(一个== null)(由于前面的xor,两个也将为null) 通过用T替换String来扩展此答案很容易,将T声明为> ...,然后我们可以安全地比较任何可为空的Comparable对象

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

这就是我最终所追求的。事实证明,我们已经有一个用于null安全的String比较的实用程序方法,因此最简单的解决方案就是利用它。 (这是一个很大的代码库;很容易错过这种事情:)

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

这是定义助手的方式(它已重载,因此您可以根据需要定义空值是第一个还是最后一个):

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

因此,这与Eddie的答案(虽然我不会将静态辅助方法称为比较器)和uzhin的答案基本相同。

总之,总的来说,我会强烈支持Patrick的解决方案,因为我认为在可能的情况下使用已建立的库是一个好习惯。 (如Josh Bloch所说,知道并使用这些库。)但是在这种情况下,这将不会产生最干净,最简单的代码。

编辑(2009):Apache Commons Collections版本

实际上,这是一种使基于Apache Commons NullComparator的解决方案更简单的方法。将其与String类中提供的不区分大小写的Comparator结合使用:

1234567891011public static final Comparator 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):番石榴版本

将近5年后,这就是我如何解决最初的问题。如果使用Java进行编码,那么(当然)我将使用Guava。 (当然可以肯定不是Apache Commons。)

将此常数放在某处,例如在" StringUtils"类中:

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

然后,在public class Metadata implements Comparable中:

12345678@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()链接多个订购。阅读订购说明,了解更多!

相关讨论 String.CASE_INSENSITIVE_ORDER确实使解决方案更加简洁。不错的更新。 如果仍然使用Apache Commons,则有一个ComparatorChain,因此您不需要自己的compareTo方法。

我总是建议使用Apache Commons,因为它很可能比您自己编写的更好。另外,您可以进行"实际"工作,而不是重新发明。

您感兴趣的类是Null比较器。它允许您将null设置为高或低。当两个值都不为null时,您还可以给它自己使用比较器。

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

有点像

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

}

即使您决定自己滚动,也请记住该类,因为在订购包含空元素的列表时它非常有用。

相关讨论 谢谢,我有点希望下议院会有这样的事情!但是,在这种情况下,我并没有最终使用它:stackoverflow.com/questions/481813/ 刚刚意识到,可以使用String.CASE_INSENSITIVE_ORDER简化您的方法;看到我编辑的后续答案。 +1表示不重新发明轮子 这很好,但是" if(other == null){"检查不应该存在。适用于Comparable的Javadoc说,如果other为null,则compareTo应该抛出NullPointerException。

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

但我只想指出,在compareTo中支持null并不符合Comparable官方javadocs中描述的compareTo协定:

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.

所以我要么显式抛出NullPointerException,要么在取消引用null参数时让它第一次抛出。

您可以提取方法:

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

}

相关讨论 不应将" 0:1"设为" 0:-1"吗?

您可以将您的类设计为不可变的(Effective Java 2nd Ed。对此有很大的介绍,第15项:最小化可变性),并在构造时确保没有空值是可能的(并在需要时使用空对象模式)。然后,您可以跳过所有这些检查,并安全地假定值不为null。

相关讨论 是的,那通常是一个很好的解决方案,并且简化了很多事情-但在这里,我对出于某种原因而允许空值并且必须考虑空值的情况更感兴趣:)

我们可以使用Java 8在对象之间进行null友好的比较。 假设我有一个具有2个字段的Boy类:字符串名称和整数年龄,我想先比较名称,然后如果年龄相等则比较年龄。

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748static void test2() {     List 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 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;     } }

结果:

1234567891011    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

我正在寻找类似的东西,这似乎有点复杂,所以我做到了。我认为这有点容易理解。您可以将其用作比较器或一根衬管。对于此问题,您将更改为compareToIgnoreCase()。照原样,空值会浮起来。如果您希望它们下沉,可以将1,-1翻转。

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

123456789101112131415161718192021222324252627public class StringUtil {     public static final Comparator NULL_SAFE_COMPARATOR = new Comparator() {         @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 list = new ArrayList(Arrays.asList(new String[]{"qad","bad","sad", null,"had"}));         Collections.sort(list, NULL_SAFE_COMPARATOR);         System.out.println(list);     } }

使用NullSafe Comparator的一种简单方法是使用它的Spring实现,以下是要引用的一种简单示例:

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

如果有人使用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

12345678910111213141516171819202122232425262728293031323334import 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 list = new ArrayList();         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);         }     } }

输出是

123456Student [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]

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

123456@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,而我的容器是List 。

" compareIfNull()"方法用于安全空排序。其他方法用于完整性,说明如何使用compareIfNull。

123456789101112131415161718192021222324252627282930313233343536@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 AbsComparatorByName = = new Comparator() {     @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; }

对于特定的情况,您知道数据不会包含null(对于字符串来说总是个好主意),并且数据确实很大,如果确实确定是这种情况,那么在实际比较值之前,您仍然需要进行三次比较,您可以优化一点。 YMMV作为可读代码胜过较小的优化:

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



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3