具有空值的比较器
我们有一些代码根据它们的坐标之间的距离对地址列表进行sorting。 这是通过与自定义比较器的collections.sort完成的。
然而,不时有一个没有坐标的地址出现在导致NullPointerException的列表中。 我最初的想法是解决这个问题,让比较器返回0作为至less有一个坐标为空的地址的距离。 我担心这可能会导致列表中“有效”元素的腐败。
所以在比较器中返回空值数据的“0”值,还是有一个更清晰的方法来解决这个问题。
像null
这样处理意味着无限远。 从而:
-
comp(1234, null) == -1
-
comp(null, null) == 0
-
comp(null, 1234) == 1
有了这个,你得到一个一致的顺序。
为了扩展WilliSchönborn的回答,我来到这里说谷歌收集正是你在这里之后。
在一般情况下,您可以编写自己的Comparator
来忽略空值(假设为非null,因此它可以专注于重要的逻辑),然后使用Ordering来处理空值:
Collections.sort(addresses, Ordering.from(new AddressComparator()).nullsLast());
在你的情况,虽然,它是数据在地址(坐标)被用来sorting,对吧? 谷歌集合在这种情况下更加有用。 所以你可能会更喜欢一些东西:
// Seems verbose at first glance, but you'll probably find yourself reusing // this a lot and it will pay off quickly. private static final Function<Address, Coordinates> ADDRESS_TO_COORDINATES = new Function<Address, Coordinates>() { public Coordinates apply(Address in) { return in.getCoordinates(); } }; private static final Comparator<Coordinates> COORDINATE_SORTER = .... // existing
那么当你想sorting:
Collections.sort(addresses, Ordering.from(COORDINATE_SORTER) .nullsLast() .onResultOf(ADDRESS_TO_COORDINATES));
这正是谷歌collections的力量真正开始回报的地方。
我承认这一点是,你试图做的任何事情“做好” null
坐标只是在裂缝的纸张。 你真正需要做的是find并修复注入虚假null
坐标的错误。
根据我的经验,NPE错误的感染经常是由以下糟糕的编码习惯造成的:
- input参数validation不充分,
- 使用
null
来避免创build空数组或集合, - 应该抛出exception的时候返回
null
,或者 - 当有更好的解决scheme时,使用
null
来表示“没有价值”。
(对“无价值”问题的更好的解决scheme通常涉及重写代码,以便您不需要代表这个和/或使用非空值,例如空string,特殊实例,保留值。总是find一个更好的解决scheme,但你经常可以。)
如果这描述了你的应用程序,你应该花时间根除代码问题,而不是想办法隐藏NPE。
我的解决scheme(可能是有用的人在这里看)是做正常的比较,空值不是0,而是最大值可能(如Integer.MAX_VALUE)。 返回0是不一致的,如果你有它们自己的值0.这里是一个正确的例子:
public int compare(YourObject lhs, YourObject rhs) { Integer l = Integer.MAX_VALUE; Integer r = Integer.MAX_VALUE; if (lhs != null) { l = lhs.giveMeSomeMeasure(); } if (rhs != null) { r = rhs.giveMeSomeMeasure(); } return l.compareTo(r); }
我只是想补充说,你不需要整数的最大值。 这取决于你的giveMeSomeMeasure()方法可以返回什么。 例如,如果比较天气的摄氏度,则可以将l和r分别设置为-300或+300,具体取决于要设置空对象的位置 – 列表的头部还是尾部。
你可能不想返回0,因为这意味着地址是等距的,你真的不知道。 这是一个相当经典的问题,你正在努力处理不良的input数据。 当你不知道距离的时候,我不认为它比较器的责任是试图确定地址的实际含义。 在sorting之前,我会从列表中删除这些地址。
黑客将是将他们移动到名单的底部(但那是丑陋的!)
不要把这看成是比较器的技术问题,而是再次考虑一下需求:你真正想在这里做什么,你将如何处理这个有序列表?
- 如果您正在尝试对它们进行sorting以首先向用户显示最相关的解决scheme,那么最好将未知位置放在最后,因此请将其视为无穷大(根据其中哪些位置返回0 / -1 / 1空值)。
- 如果你打算使用这个结果来画一些图或者做一些其他的计算,而这些计算是依靠它们的距离来确定的,那么空值可能不应该在那里(所以要么先删除它们,要么扔一个例外,如果在这一点上,实际上不应该有任何空地点的地址)。
正如你已经意识到的那样,当其中一个为空时总是返回0在这里不是一个好主意; 它确实会损害结果。 但是,你应该做什么取决于你需要什么,而不是别人通常做/需要什么。 您的程序如何处理没有位置的地址(用户将看到的地址)应该不取决于某些技术细节,比如比较器的“最佳实践”是什么。 (对我来说,问这是什么“最佳实践”,听起来像问什么是“最好的要求”)。
不,没有更干净的方法。 也许:
- 如果两个比较对象的坐标都为空,则返回0
- 如果其中一个对象的坐标为空,则返回-1 / 1(取决于它是第一个还是第二个参数)
但更重要的是 – 试着摆脱/填写缺失的坐标,或者更好的是:不要在地址列表中丢失坐标。
其实,不要把它们列入名单是最合乎逻辑的行为。 如果将它们放在列表中,结果将不会按距离sorting。
您可以创build另一个列表,包含缺less坐标的地址,并向需要该信息的任何人(最终用户,API用户)说明第一个列表只包含具有所需数据的地址,而第二个列表包含地址缺乏必要的信息。
我个人讨厌在我的比较器中到处处理特殊的空案例,所以我正在寻找一个更清晰的解决scheme,并最终find谷歌collections。 他们的订单是真棒。 它们支持复合比较器,在比较之前提供对空值进行sorting并允许运行某些function。 写作比较从未如此简单。 你应该试试看。
如果您使用的是Java 8,则在Comparator类中有两个新的静态方法,它们派上用场:
public static <T> Comparator<T> nullsFirst(Comparator<? super T> comparator) public static <T> Comparator<T> nullsLast(Comparator<? super T> comparator)
比较将是无效的,您可以select将空值放入sorting序列的位置。
下面的例子:
List<String> monkeyBusiness = Arrays.asList("Chimp", "eat", "sleep", "", null, "banana", "throw banana peel", null, "smile", "run"); Comparator<? super String> comparator = (a, b) -> a.compareTo(b); monkeyBusiness.stream().sorted(Comparator.nullsFirst(comparator)) .forEach(x -> System.out.print("[" + x + "] "));
将会打印:[null] [null] []黑猩猩[banana] [吃] [run] [睡觉] [smile] [扔香蕉皮]