我有一个关于使用新的Objects.compare(o1,o2,Comparator)方法的问题 - 从我自己的测试中,如果o1和o2都为null,那么它返回0,但是,如果其中一个为null然后它仍然抛出一个空指针异常。我在Objects.equals和一些其他的Objects实用程序方法上找到了很多材料,但在Objects.compare上没有太多内容,当我们需要使用它/用它替换旧代码时。
所以我可以这样做:
String s1 = "hi";
String s2 = "hi";
int x = Objects.compare(s1, s2, Comparator.naturalOrder());
System.out.println("x = " + x);
工作正常,返回0,现在这个:
String s1 = null;
String s2 = null;
也正常工作并返回0.但是,这个:
String s1 = "hi";
Strng s2 = null;
抛出NullPointerException。我猜测Objects.compare(o1,o2,Comparator)与o1.compareTo(o2)的好处是它至少处理两个对象都为空的情况,当其中一个为null时,它允许你设计一个比较器处理它。我想,例如。
int x = Objects.compare(s1, s2, Comparator.nullsFirst(Comparator.naturalOrder()));
而使用x.compareTo(y)除非事先这样做,否则无法处理null?那么Java库开发人员现在是否打算在我们关注null时将所有对compareTo的调用替换为Objects.compare?例如我们会在可比较的实现中执行此操作吗?
侧查询1:关于使用nullsFirst,如果你使用它然后传入一个Comparator,它使用比较链接,然后比较等,是否适用于所有内部比较器? e.g。
Comparator.nullsFirst(Comparator.comparing(Song::getTitle)
.thenComparing(Song::getArtist)
.thenComparing(Song::getDuration)
)
是否会将nullsFirst应用于内部的所有内容,或者您是否需要在每个内容中单独使用nullsFirst?我认为从测试它只适用于实际的Song对象为null,而不是标题或艺术家的字段为null,即如果它们为null,则仍然抛出NullPointerException。不管怎么说?
旁边查询2:最后一个问题是,因为我喜欢Comparator.comparing语法,我建议开始使用它来编写我的compareTo实现 - 我正在努力思考如何替换这种传统方法,例如
public int compareTo(Song other) {
int result = this.title.compareTo(other.title);
if (result == 0) {
result = this.artist.compareTo(other.artist);
if (result == 0) {
result = Integer.compare(this.duration, other.duration);
}
}
return result;
}
然后我想我可以使用Objects.compare(...),如下所示:
public int compareTo(Song other) {
return Objects.compare(this, other, Comparator.nullsFirst(
Comparator.comparing(Song::getTitle)
.thenComparing(Song::getArtist)
.thenComparingInt(Song::getDuration)
));
}
我认为这个版本更优雅 - 我假设它正如我认为的那样工作,例如通过传递这个和其他作为前两个参数然后比较器,它与传统compareTo方法与if语句具有相同的效果?虽然我可以看到Objects.compare捕获两个空值的好处永远不会发生,因为它是null,然后将永远不会达到compareTo方法调用(通过处理异常或它被抛出)。但是通过使用nullsFirst,我想如果传入的参数,即其他参数为null,那么这会安全地处理它吗?
非常感谢您的任何帮助。
答案 0 :(得分:4)
Objects.compare
并不意味着提供null
安全比较,因为没有可以实现的默认行为。它只是实现了当两个对象相同时不调用Comparator
方法的快捷方式。换句话说,它只是a==b? 0: c.compare(a, b)
,仅此而已。因此,当两个对象都是null
时,不要破坏只是一种副作用。封装的代码可能看起来微不足道,但此类中的其他方法属于类似的类别。使用小实用程序方法可能仍会导致显着的胜利。
顺便说一句,它根本不是Java 8方法。它存在于Java 7之后。
关于您的第二个问题,Comparator.nullsFirst(…)
会修饰现有的Comparator
,并会在委派给提供的比较器之前强制执行null
值的规则,因为它是这个比较器的目的是保护现有的比较器不会看到null
值。装饰的比较器是否是链式比较器并不重要。只要它就是你所谓的“内部比较器”,就像
你不得在thenComparing
的结果上调用nullsFirst
,因为这意味着当两个值均为{{1}时调用下一个比较器}}
null
现在回答第三个问题,使用Comparator.nullsFirst(Comparator.comparing(a).thenComparing(b)) // perfect
Comparator.nullsFirst(Comparator.comparing(a)).thenComparing(b) // ouch
比较器实施compareTo
方法违反了interface
specification:
实施者必须确保所有
nullsFirst
和sgn(x.compareTo(y)) == -sgn(y.compareTo(x))
x
。 (这意味着如果y
抛出异常,x.compareTo(y)
必须抛出异常。)
这意味着传递y.compareTo(x)
作为参数应始终导致null
作为交换参数,接收者也会无条件地抛出。
包含NullPointerException
政策的订单应始终作为单独的null
提供。
请注意,为每次 Comparator
调用创建一个新的Comparator
(确切地说是多个Comparators
),效率也会非常低。现在图像排序这些对象的相当大的列表......
答案 1 :(得分:1)
我通常为你的最后一个问题做的是首先在类中创建一个静态比较器引用:
public static final Comparator<Song> COMP_DEFAULT
= nullsFirst(comparing(Song::getTitle, nullsFirst(naturalOrder()))
.thenComparing(Song::getArtist, nullsFirst(naturalOrder()))
.thenComparingInt(Song::getDuration));
然后在compareTo
public int compareTo(Song other) {
return COMP_DEFAULT.compare(this, other);
}
这样您就不会为每次compareTo
来电重新创建比较器,Song
保证a.comparetTo(b) == b.compareTo(a)
的空安全性。{/ 1}}。
我们还通过对传入的密钥比较器(第二个参数)使用nullsFirst(naturalOrder())
来确保每个属性的空安全性。
由于返回的Comparator
是不可变的,因此可以将其公开,这可以方便地将一些备用Comparator
与消费者可能使用的类捆绑在一起。