如何使用比较器比较空值?

发布于 2024-08-10 08:56:28 字数 1544 浏览 2 评论 0原文

我有几个比较器——一个用于日期,一个用于小数,一个用于百分比,等等。

开始我的小数比较器看起来像这样:

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 Comparators -- one for Dates, 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 nulls?"

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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(12

泛滥成性 2024-08-17 08:56:28

您正在实现一个ComparatorString 的方法(包括 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);
  }
}

通过提取方法来进行类型检查和转换,你几乎可以恢复原来的优雅。

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”参数。我相信你可以通过应用一些小技巧来失去这里多余的一行或那里多余的单词。

您说您需要与其他三个类重复此模式,这些类比较不同类型的字符串并可能引发其他三个异常。在没有看到情况的情况下很难提供具体细节,但是您可以使用 "Pull Up 方法” 将我的 parseStringAsDouble 版本转换为 NumericComparator 的共同祖先,它本身实现了 java 的 Comparator

You are implementing a Comparator<String>. String's methods, including compareTo throw a NullPointerException if a null is handed in to them, so you should too. Similarly, Comparator throws a ClassCastException if the arguments' types prevent them from being compared. I would recommend you implement these inherited behaviors.

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

You can almost regain the original elegance by extracting a method to do the type checking and conversion.

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

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 my parseStringAsDouble into a common ancestor of NumericComparator that itself implements java's Comparator.

失眠症患者 2024-08-17 08:56:28

这个问题有很多主观答案。这是我自己的 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.

雨落星ぅ辰 2024-08-17 08:56:28

以下是我改进比较器的方法:

首先,提取用于转换值的方法。它被重复,多次尝试...捕获总是丑陋的 ->最好是尽可能少的。

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

...是的,这是一个分支嵌套三元。如果有人对此抱怨,请说出这里其他人的说法:处理 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.

private Double getDouble(String number) {
 try {
  return Double.parseDouble(number);
 } catch(NumberFormatException e) {
  return null;
 }
}

Next, write down simple rules to show how you want the flow of the comparator to be.

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

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"):

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

...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.

水波映月 2024-08-17 08:56:28

您可以创建一个实用程序方法来处理解析并在出现空值或解析异常时返回特定值。

You could create a utility method that handles parsing and returns a certain value in the case of nulls or parse exceptions.

久夏青 2024-08-17 08:56:28

退一步。这些字符串从哪里来?这个Comparator有什么用?您是否有想要排序的字符串集合

Take a step back. Where does those Strings come from? For what is this Comparator to be used? Do you have a Collection of Strings which you would like to sort or so?

东京女 2024-08-17 08:56:28

试试这个:

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

如果您认为的话,唯一的问题是空字符串和其他不可解析的字符串将全部混合在一起。考虑到好处,这可能不是什么大问题——这为您提供了一个保证正确的比较器,而使用手工编码的比较器,即使是相对简单的比较器,令人惊讶的是,很容易犯一个微妙的错误,从而破坏传递性,或者,嗯,反对称性。

http://google-collections.googlecode.com

Try this:

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

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

一笑百媚生 2024-08-17 08:56:28

似乎这里混合了两个问题,也许应该分解为单独的组件。请考虑以下事项:

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 接口将具有数字、日期等的实现。您可能会为 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:

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();
  }
}

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.

溺渁∝ 2024-08-17 08:56:28

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" and null, which is greater? The JDK source doesn't handle NullPointerExceptions in the Comparator: if you give it a null, autoboxing will fail.

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.

Exactly why I would argue that the parsing should happen outside your Comparator with exception-handling dealt with before it arrives at your code.

无尽的现实 2024-08-17 08:56:28

如果您能够更改签名,我建议您编写该方法,以便它可以接受任何支持的对象。

  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");
  }

您甚至可以递归地定义它,将字符串转换为双精度数,然后返回使用这些对象调用自身的结果。

If you are able to change the signature I would suggest you write the method so that it can accept any supported Object.

  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");
  }

You might even define it recursively where you cast your Strings to Doubles and then return the result of calling itself with those objects.

友谊不毕业 2024-08-17 08:56:28

恕我直言,您应该首先创建一个从字符串返回 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 ?

说不完的你爱 2024-08-17 08:56:28

根据您的需求和 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;
    }

}

我将该方法提取为抽象类,可以在其他情况下重用(尽管类名很糟糕)。

干杯。

according to your needs and Ewan's post, I think there's a way to extract the structure that you can reuse:

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

}

I extract the method as an abstract class which can be reuse in other cases(although the class name is suck).

cheers.

心房敞 2024-08-17 08:56:28

所以我改进了compare()...

当然你也这么做了。

首先,Comparator 接口没有指定空值会发生什么。如果您的 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;  
 }
}

似乎您可能会发现将 test 重命名为 t1 并将 retVal 重命名为 q 更清晰。

至于重复模式...呃。您也许可以使用带有反射的泛型来调用适当的 parseX 方法。似乎这不值得。

So I improved compare()...

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?

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

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文