我有一些Comparator
s - 一个用于Date
s,一个用于小数,一个用于百分比等。
起初我的小数比较器看起来像这样:
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
s怎么样?”
很好,所以现在我必须用NullPointerException
重复上述内容,或者在方法主体前加上:
if (s1 == null) {
if (s2 == null) {
return 0;
} else {
return -1;
}
} else if (s2 == null) {
return 1;
}
这种方法很庞大。最糟糕的是,我需要用三个其他类重复这个模式,它比较不同类型的字符串,并且可以在解析时引发三个其他异常。
我不是Java专家。是否有一个更清洁,更整洁的解决方案 - gasp - 复制和粘贴?我是否应该因缺乏复杂性而交换正确性,只要有文件记录?
更新:有人建议处理Comparator
值不是null
的工作。由于排序结果显示给用户,我确实希望空值一致。
答案 0 :(得分:4)
您正在实施Comparator<String>
。 String
的方法,包括compareTo
如果将null传递给NullPointerException
,那么你也应该这样做。同样,如果参数的类型阻止它们被比较,Comparator
会抛出ClassCastException
。我建议你实现这些继承的行为。
class NumericComparator implements Comparator<String> {
public int compare(String s1, String s2) {
final Double i1;
final Double i2;
if(s1 == null)
{
throw new NullPointerException("s1 is null"); // String behavior
}
try {
i1 = Double.parseDouble(s1)
} catch (NumberFormatException e) {
throw new ClassCastException("s1 incorrect format"); // Comparator behavior
}
if(s2 == null)
{
throw new NullPointerException("s2 is null"); // String behavior
}
try {
i2 = Double.parseDouble(s1)
} catch (NumberFormatException e) {
throw new ClassCastException("s2 incorrect format"); // Comparator behavior
}
return i1.compareTo(i2);
}
}
您可以通过extracting a method 几乎恢复原始的优雅,进行类型检查和转换。
class NumericComparator implements Comparator<String> {
public int compare(String s1, String s2) {
final Double i1;
final Double i2;
i1 = parseStringAsDouble(s1, "s1");
i2 = parseStringAsDouble(s2, "s2");
return i1.compareTo(i2);
}
private double parseStringAsDouble(String s, String name) {
Double i;
if(s == null) {
throw new NullPointerException(name + " is null"); // String behavior
}
try {
i = Double.parseDouble(s1)
} catch (NumberFormatException e) {
throw new ClassCastException(name + " incorrect format"); // Comparator behavior
}
return i;
}
}
如果您不特别关注异常消息,则可能会丢失“name”参数。我相信你可以在这里输掉额外的一行,或者通过应用小技巧来说明。
你说你需要repeat this pattern with three other classes which compare different types of strings and could raise three other exceptions
。在没有看到情况的情况下很难在那里提供细节,但是你可以在我的parseStringAsDouble
版本上使用"Pull Up Method"到NumericComparator
的共同祖先,它本身实现了java {{1} }}
答案 1 :(得分:2)
这个问题有很多主观的答案。这是我自己的$ .02。
首先,您所描述的问题是缺乏一流功能的语言的规范症状,这将使您能够简洁地描述这些模式。
其次,在我看来,如果其中一个字符串不能被视为double的表示,那么应将两个字符串比作双打是错误的。 (对于null等也是如此)因此,您应该允许异常传播!我希望,这将是一个有争议的意见。
答案 2 :(得分:1)
您可以创建一个处理解析的实用程序方法,并在空值或解析异常的情况下返回某个值。
答案 3 :(得分:1)
退后一步。那些Strings
来自哪里?这个Comparator
的用途是什么?你有Collection
Strings
你想要排序吗?
答案 4 :(得分:1)
tl; dr:从JDK获取指导。 Double比较器未定义为非数字或空值。让人们为您提供有用的数据(双打,日期,恐龙,等等)并为此编写比较器。
尽我所知,这是一个用户输入验证的案例。例如,如果您从对话框中获取输入,则确保您具有可解析的字符串,该字符串是Double,Date或输入处理程序中的任何内容。在用户可以选择之前确定它是好的,点击“Okay”或等效。
这就是为什么我认为这个:
第一个问题:如果字符串不能作为数字解析,我认为你试图在错误的地方解决问题。比如说,我尝试将"1.0"
与"Two"
进行比较。第二个显然不能解析为Double,但它是否比第一个少?或者它更大。我认为用户应该在他们问你哪个更大之前将他们的Strings变成双打(例如你可以用Double.compareTo轻松回答)。
第二个问题:如果字符串是"1.0"
和null
,哪个更大? JDK源代码不处理Comparator中的NullPointerExceptions:如果给它一个null,则autoboxing将失败。
最糟糕的是,我需要重复一遍 这个模式与其他三个类 比较不同类型的 字符串,可以提出另外三个 解析时的异常。
正是为什么我认为解析应该在比较器之外进行,异常处理在它到达你的代码之前处理。
答案 5 :(得分:1)
试试这个:
import com.google.common.base.Function;
import com.google.common.collect.Ordering;
Ordering.nullsFirst().onResultOf(
new Function<String, Double>() {
public Double apply(String s) {
try {
return Double.parseDouble(s);
} catch (NumberFormatException e) {
return null;
}
})
唯一的问题,如果您认为是这样的话,那么null字符串和其他不可解析的字符串将全部混合在一起。这可能不是什么大不了的,考虑到它的好处 - 这给你一个保证正确的比较器,而使用手动编码的比较器,即使是相对简单的比较器,令人惊讶的是提交一个细微的错误是多么容易传递性,或者,反对称性。
答案 6 :(得分:1)
以下是我改进比较器的方法:
首先,提取转换值的方法。这是重复的,多次尝试......捕获总是很难看 - &gt;最好尽量少用它们。
private Double getDouble(String number) {
try {
return Double.parseDouble(number);
} catch(NumberFormatException e) {
return null;
}
}
接下来,写下简单的规则来说明你希望比较器的流程如何。
if i1==null && i2!=null return -1
if i1==null && i2==null return 0
if i1!=null && i2==null return 1
if i1!=null && i2!=null return comparison
最后对实际的比较器进行了可怕的混淆,以便在代码审查中引发一些WTF:或像其他人喜欢说的那样,“实施比较器”:
class NumericComparator implements Comparator<String> {
public int compare(String s1, String s2) {
final Double i1 = getDouble(s1);
final Double i2 = getDouble(s2);
return (i1 == null) ? (i2 == null) ? 0 : -1 : (i2 == null) ? 1 : i1.compareTo(i2);
}
private Double getDouble(String number) {
try {
return Double.parseDouble(number);
} catch(NumberFormatException e) {
return null;
}
}
}
...是的,这是一个分支嵌套的三元组。如果有人抱怨它,请说出其他人在说什么:处理空值不是比较者的工作。
答案 7 :(得分:1)
似乎有两个问题在这里混合,也许应该分解成单独的组件。请考虑以下事项:
public class ParsingComparator implements Comparator<String> {
private Parser parser;
public int compare(String s1, String s2) {
Object c1 = parser.parse(s1);
Object c2 = parser.parse(s2);
new CompareToBuilder().append(c1, c2).toComparison();
}
}
Parser接口可以实现数字,日期等。您可以使用java.text.Format类作为Parser接口。如果你不想使用commons-lang,你可以用一些逻辑来替换CompareToBuilder来处理空值,并使用Comparable而不是Object来表示c1和c2。
答案 8 :(得分:0)
如果您能够更改签名,我建议您编写该方法,以便它可以接受任何受支持的对象。
public int compare(Object o1, Object o2) throws ClassNotFoundException {
String[] supportedClasses = {"String", "Double", "Integer"};
String j = "java.lang.";
for(String s : supportedClasses){
if(Class.forName(j+s).isInstance(o1) && Class.forName(j+s).isInstance(o1)){
// compare apples to apples
return ((Comparable)o1).compareTo((Comparable)o2);
}
}
throw new ClassNotFoundException("Not a supported Class");
}
您甚至可以递归地将字符串转换为双打,然后返回使用这些对象调用自身的结果。
答案 9 :(得分:0)
恕我直言你应该首先创建一个从String返回Double的方法,嵌入null并解析失败的情况(但你必须定义在这种情况下要做什么:抛出异常?返回默认值?)。 / p>
然后你的比较器只需比较获得的Double实例。
换句话说,重构......
但我仍然想知道为什么你需要比较字符串,虽然期望它们代表双打。我的意思是,什么阻止你在实际使用这个比较器的代码中操纵双打?
答案 10 :(得分:0)
根据您的需求和Ewan的帖子,我认为有一种方法可以提取您可以重复使用的结构:
class NumericComparator implements Comparator<String> {
private SafeAdaptor<Double> doubleAdaptor = new SafeAdaptor<Double>(){
public Double parse(String s) {
return Double.parseDouble(s);
}
};
public int compare(String s1, String s2) {
final Double i1 =doubleAdaptor.getValue(s1, "s1");
final Double i2 = doubleAdaptor.getValue(s2, "s2");
return i1.compareTo(i2);
}
}
abstract class SafeAdaptor<T>{
public abstract T parse(String s);
public T getValue(String str, String name) {
T i;
if (str == null) {
throw new NullPointerException(name + " is null"); // String
}
try {
i = parse(str);
} catch (NumberFormatException e) {
throw new ClassCastException(name + " incorrect format"); // Comparator
}
return i;
}
}
我将该方法提取为抽象类,在其他情况下可以重用(尽管类名很糟糕)。
欢呼声。
答案 11 :(得分:-3)
确定你做到了。所以我改进了compare()...
首先,Comparator接口没有指定null的内容。如果您的null检查if语句是否适用于您的用例,那很好,但一般的解决方案是抛出一个npe。
至于清洁......为什么最后?为什么所有的捕获/抛出?为什么使用compareTo作为原始包装器?
class NumericComparator implements Comparator<String> {
public int compare(String s1, String s2) throws NullPointerException, NumberFormatException {
double test = Double.parseDouble(s1) - Double.parseDouble(s2);
int retVal = 0;
if (test < 0) retVal = -1;
else if (test > 0) retVal = 1;
return retVal;
}
}
您似乎可以更清楚地将测试重命名为 t1 ,将 retVal 重命名为 q 。
重复模式...呃。您可以使用带有反射的泛型来调用适当的 parseX 方法。虽然看起来不值得。