如何使用比较器比较空值?
我有几个比较器
——一个用于日期
,一个用于小数,一个用于百分比,等等。
开始我的小数比较器看起来像这样:
class NumericComparator implements Comparator<String> {
@Override
public int compare(String s1, String s2) {
final Double i1 = Double.parseDouble(s1);
final Double i2 = Double.parseDouble(s2);
return i1.compareTo(i2);
}
}
一 简单的。当然,这不能处理字符串不可解析的情况。所以我改进了 compare()
:
class NumericComparator implements Comparator<String> {
@Override
public int compare(String s1, String s2) {
final Double i1;
final Double i2;
try {
i1 = Double.parseDouble(s1);
} catch (NumberFormatException e) {
try {
i2 = Double.parseDouble(s2);
} catch (NumberFormatException e2) {
return 0;
}
return -1;
}
try {
i2 = Double.parseDouble(s2);
} catch (NumberFormatException e) {
return 1;
}
return i1.compareTo(i2);
}
}
生活变得更好了。测试感觉更扎实。然而,我的代码审阅者指出,“null
怎么样?”
太棒了,所以现在我必须使用 NullPointerException
重复上述内容,或者在方法主体前面加上:
if (s1 == null) {
if (s2 == null) {
return 0;
} else {
return -1;
}
} else if (s2 == null) {
return 1;
}
这个方法很大。最糟糕的是,我需要与其他三个类重复此模式,这些类比较不同类型的字符串,并可能在解析时引发其他三个异常。
我不是 Java 专家。有没有比复制和粘贴更干净、整洁的解决方案?只要有记录,我是否应该用正确性来换取不复杂性?
更新:有些人认为处理 null
值不是 Comparator
的工作。由于排序结果显示给用户,我确实希望对空值进行一致排序。
I've got a few Comparator
s -- one for Date
s, one for decimals, one for percentages, etc.
At first my decimal comparator looked like this:
class NumericComparator implements Comparator<String> {
@Override
public int compare(String s1, String s2) {
final Double i1 = Double.parseDouble(s1);
final Double i2 = Double.parseDouble(s2);
return i1.compareTo(i2);
}
}
Life was simple. Of course, this doesn't handle the case where the strings aren't parseable. So I improved compare()
:
class NumericComparator implements Comparator<String> {
@Override
public int compare(String s1, String s2) {
final Double i1;
final Double i2;
try {
i1 = Double.parseDouble(s1);
} catch (NumberFormatException e) {
try {
i2 = Double.parseDouble(s2);
} catch (NumberFormatException e2) {
return 0;
}
return -1;
}
try {
i2 = Double.parseDouble(s2);
} catch (NumberFormatException e) {
return 1;
}
return i1.compareTo(i2);
}
}
Life was better. Tests felt more solid. However, my code reviewer pointed out, "What about null
s?"
Great, so now I have to repeat the above with NullPointerException
or prepend the method body with:
if (s1 == null) {
if (s2 == null) {
return 0;
} else {
return -1;
}
} else if (s2 == null) {
return 1;
}
This method is huge. The worst part is, I need to repeat this pattern with three other classes which compare different types of strings and could raise three other exceptions while parsing.
I'm not a Java expert. Is there a cleaner, neater solution than -- gasp -- copying and pasting? Should I trade correctness for lack of complexity so as long as it is documented?
Update: Some have suggested that it's not the Comparator
's job to handle null
values. Since the sort results are displayed to users I indeed want nulls to be sorted consistently.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(12)
您正在实现一个
Comparator
。String
的方法(包括compareTo
)如果向它们传递 null,则会抛出NullPointerException
,所以您也应该这么做。同样,如果参数的类型阻止进行比较,Comparator
会抛出ClassCastException
。我建议您实现这些继承的行为。通过提取方法来进行类型检查和转换,你几乎可以恢复原来的优雅。
如果您不特别关注异常消息,则可能会丢失“name”参数。我相信你可以通过应用一些小技巧来失去这里多余的一行或那里多余的单词。
您说您需要
与其他三个类重复此模式,这些类比较不同类型的字符串并可能引发其他三个异常
。在没有看到情况的情况下很难提供具体细节,但是您可以使用 "Pull Up 方法” 将我的parseStringAsDouble
版本转换为NumericComparator
的共同祖先,它本身实现了 java 的Comparator
。You are implementing a
Comparator<String>
.String
's methods, includingcompareTo
throw aNullPointerException
if a null is handed in to them, so you should too. Similarly,Comparator
throws aClassCastException
if the arguments' types prevent them from being compared. I would recommend you implement these inherited behaviors.You can almost regain the original elegance by extracting a method to do the type checking and conversion.
If you are not particular about the Exception messages, you can lose the "name" parameter. I'm sure you can lose an extra line here or word there by applying little tricks.
You say you need to
repeat this pattern with three other classes which compare different types of strings and could raise three other exceptions
. It's difficult to offer specifics there without seeing the situation, but you may be able to use "Pull Up Method" on a version of myparseStringAsDouble
into a common ancestor ofNumericComparator
that itself implements java'sComparator
.这个问题有很多主观答案。这是我自己的 0.02 美元。
首先,您所描述的问题是缺乏一流函数的语言的典型症状,这将使您能够简洁地描述这些模式。
其次,在我看来,如果其中一个字符串不能被视为双精度数的表示,那么将两个字符串作为双精度数进行比较应该是错误的。 (对于空值等也是如此)因此,您应该允许异常传播!我预计这将是一个有争议的观点。
There are a lot of subjective answers to this question. Here's my own $.02.
First, the trouble you're describing is the canonical symptom of a language that lacks first-class functions, which would enable you to succinctly describe these patterns.
Second, in my opinion, it should be an error to compare two Strings as Doubles if one of them cannot be considered a representation of a double. (The same is true for nulls, etc.) Therefore, you should permit the exceptions to propagate! This will be a contentious opinion, I expect.
以下是我改进比较器的方法:
首先,提取用于转换值的方法。它被重复,多次尝试...捕获总是丑陋的 ->最好是尽可能少的。
接下来,写下简单的规则来显示您希望比较器的流程如何。
最后对实际比较器进行可怕的混淆,以在代码审查中引发一些WTF:(或者像其他人喜欢说的那样,“实现比较器”):
...是的,这是一个分支嵌套三元。如果有人对此抱怨,请说出这里其他人的说法:处理 null 不是 Comparator 的工作。
Here's how I'd improve the comparator:
First, exctract a method for converting the value. It's being repeated, multiple try...catches are always ugly -> better to have as few of them as possible.
Next, write down simple rules to show how you want the flow of the comparator to be.
Finally do horrible obfuscation to the actual comparator to raise a few WTF:s in code review (or like others like to say it, "Implement the Comparator"):
...yes, that's a branching nested ternary. If anyone complains about it, say what others here have been saying: Handling nulls isn't Comparator's job.
您可以创建一个实用程序方法来处理解析并在出现空值或解析异常时返回特定值。
You could create a utility method that handles parsing and returns a certain value in the case of nulls or parse exceptions.
退一步。这些
字符串
从哪里来?这个Comparator
有什么用?您是否有想要排序的字符串
的集合
?Take a step back. Where does those
Strings
come from? For what is thisComparator
to be used? Do you have aCollection
ofStrings
which you would like to sort or so?试试这个:
如果您认为的话,唯一的问题是空字符串和其他不可解析的字符串将全部混合在一起。考虑到好处,这可能不是什么大问题——这为您提供了一个保证正确的比较器,而使用手工编码的比较器,即使是相对简单的比较器,令人惊讶的是,很容易犯一个微妙的错误,从而破坏传递性,或者,嗯,反对称性。
http://google-collections.googlecode.com
Try this:
The only problem, if it you consider it that, is that null Strings and other non-parseable Strings will all be intermingled. That's probably not a big deal, considering the benefits -- this gives you a comparator that is guaranteed to be correct, whereas with a hand-coded comparator, even relatively simple ones, it's amazing how easy it is to commit a subtle error that breaks transitivity or, umm, antisymmetricity.
http://google-collections.googlecode.com
似乎这里混合了两个问题,也许应该分解为单独的组件。请考虑以下事项:
Parser 接口将具有数字、日期等的实现。您可能会为 Parser 接口使用 java.text.Format 类。如果您不想使用 commons-lang,可以将 CompareToBuilder 的使用替换为一些处理 null 的逻辑,并使用 Comparable 而不是 c1 和 c2 的 Object。
It seems that there are two concerns being mixed here and maybe should be broken up into separate components. Consider the following:
The Parser interface would have implementations for numbers, dates, etc. You could potentially use the java.text.Format class for your Parser interface. If you don't want to use commons-lang, you could replace the use of CompareToBuilder with some logic to handle nulls and use Comparable instead of Object for c1 and c2.
tl;dr: 从 JDK 获取指导。 Double 比较器没有为非数字或空值定义。让人们给你有用的数据(双打、日期、恐龙等等)并为此编写你的比较器。
据我所知,这是用户输入验证的情况。例如,如果您从对话框中获取输入,则确保您拥有可解析的字符串(Double、Date 或输入处理程序中的任何内容)的正确位置。在用户可以按 Tab 键离开、点击“确定”或类似操作之前,请确保一切正常。
这就是我这样认为的原因:
第一个问题:如果字符串无法解析为数字,我认为您试图在错误的位置解决问题。举例来说,我尝试将
"1.0"
与"Two"
进行比较。第二个显然不能解析为 Double 但它比第一个小吗?或者说它更大。我认为用户应该先将他们的字符串转换为双精度数,然后再询问哪个更大(例如,您可以使用 Double.compareTo 轻松回答)。第二个问题:如果字符串是
"1.0"
和null
,哪个更大? JDK 源代码不处理比较器中的 NullPointerException:如果给它一个 null,自动装箱将失败。正是为什么我认为解析应该在比较器之外进行,并在到达代码之前进行异常处理。
tl;dr: Take guidance from the JDK. The Double comparator is not defined for either non-numbers or nulls. Make people give you useful data (Doubles, Dates, Dinosaurs, whatever) and write your comparators for that.
As near as I can tell, this is a case of user input validation. For example, if you are taking input from a dialog box, the correct place to ensure that you have a parseable String that is a Double, Date or whatever is in the input handler. Make sure it's good before the user can tab away, hit "Okay" or equivalent.
Here's why I think this:
First question: if the Strings aren't parseable as numbers, I think you're trying to solve the problem in the wrong place. Say, for instance, I try to compare
"1.0"
to"Two"
. The second is clearly not parseable as a Double but is it less than the first? Or is it greater. I would argue that the users should have to turn their Strings into Doubles before they ask your which is greater (which you can easily answer with Double.compareTo, for instance).Second question: if the Strings are
"1.0"
andnull
, which is greater? The JDK source doesn't handle NullPointerExceptions in the Comparator: if you give it a null, autoboxing will fail.Exactly why I would argue that the parsing should happen outside your Comparator with exception-handling dealt with before it arrives at your code.
如果您能够更改签名,我建议您编写该方法,以便它可以接受任何支持的对象。
您甚至可以递归地定义它,将字符串转换为双精度数,然后返回使用这些对象调用自身的结果。
If you are able to change the signature I would suggest you write the method so that it can accept any supported Object.
You might even define it recursively where you cast your Strings to Doubles and then return the result of calling itself with those objects.
恕我直言,您应该首先创建一个从字符串返回 Double 的方法,嵌入 null 并解析失败情况(但您必须定义在这种情况下要做什么:抛出异常?返回默认值??)。
然后你的比较器只需要比较获得的 Double 实例。
换句话说,重构......
但我仍然想知道为什么你需要比较字符串,尽管期望它们代表双精度数。我的意思是,是什么阻止您在实际使用此比较器的代码中操作双精度数?
IMHO you should first create a method that returns a Double from a String, embedding the null and parsing failure cases (but you must define what to do in such cases : throw an exception ? return a default value ??).
Then your comparator just have to compare obtained Double instances.
In other words, refactoring...
But I still wonder why you need to compare strings though expecting they represent doubles. I mean, what prevents you from manipulating doubles in the code that would actually use this comparator ?
根据您的需求和 Ewan 的帖子,我认为有一种方法可以提取您可以重用的结构:
我将该方法提取为抽象类,可以在其他情况下重用(尽管类名很糟糕)。
干杯。
according to your needs and Ewan's post, I think there's a way to extract the structure that you can reuse:
I extract the method as an abstract class which can be reuse in other cases(although the class name is suck).
cheers.
当然你也这么做了。
首先,Comparator 接口没有指定空值会发生什么。如果您的 null 检查 if 语句适用于您的用例,那很好,但一般的解决方案是抛出 npe。
至于清洁剂...为什么是最终的?为什么所有的捕获/抛出?为什么使用compareTo作为原始包装器?
似乎您可能会发现将 test 重命名为 t1 并将 retVal 重命名为 q 更清晰。
至于重复模式...呃。您也许可以使用带有反射的泛型来调用适当的 parseX 方法。似乎这不值得。
sure you did.
first, the Comparator interface doesn't specify what happens with nulls. if your null checking if statement works for your use case, that's great, but the general solution is throwing an npe.
as to cleaner... why final? why all the catch/throws? why use compareTo for a primitive wrapper?
seems you might find it clearer renaming test to t1 and retVal to q.
as to repeating the pattern... eh. you might be able to use generics with reflection to invoke appropriate parseX methods. seems like that'd not be worth it though.