簡體   English   中英

如何使用Comparator比較空值?

[英]How can I compare null values using Comparator?

我有幾個Comparator - 一個用於Date ,一個用於小數,一個用於百分比等。

起初我的十進制比較器看起來像這樣:

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專家。 有沒有更清潔,更整潔的解決方案而不是 - 喘氣 - 復制和粘貼? 只要記錄在案,我是否應該因缺乏復雜性而交換正確性?


更新:有人建議處理null值不是Comparator的工作。 由於排序結果顯示給用戶,我確實希望空值一致排序。

您正在實現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);
  }
}

通過提取進行類型檢查和轉換的方法 ,您幾乎可以重新獲得原始的優雅。

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的Comparator

這個問題有很多主觀的答案。 這是我自己的$ .02。

首先,您所描述的問題是缺乏一流功能的語言的規范症狀,這將使您能夠簡潔地描述這些模式。

其次,在我看來,將兩個字符串比作雙打應該是一個錯誤,如果其中一個不能被視為雙重的表示。 (對於null等也是如此)因此,您應該允許異常傳播! 我希望這將是一個有爭議的意見。

您可以創建一個處理解析的實用程序方法,並在null或解析異常的情況下返回某個值。

退后一步。 這些Strings來自哪里? 這個Comparator器用的是什么? 你有一個你想要排序的Strings Collection嗎?

tl; dr:接受JDK的指導。 Double比較器未定義為非數字或空值。 讓人們為您提供有用的數據(雙打,日期,恐龍等等)並為此編寫比較器。

就像我所知,這是一個用戶輸入驗證的情況。 例如,如果您從對話框中獲取輸入,則確保您具有可解析的字符串,該字符串是Double,Date或輸入處理程序中的任何內容。 確保它在用戶可以退出,點擊“Okay”或等效之前是好的。

這就是為什么我認為這個:

第一個問題:如果字符串不能作為數字解析,我認為你試圖在錯誤的地方解決問題。 比方說,我試着比較"1.0""Two" 第二個顯然不能解析為Double,但它是否比第一個少? 或者它更大。 我認為用戶應該在他們問你哪個更大之前將他們的Strings變成雙打(例如你可以用Double.compareTo輕松回答)。

第二個問題:如果字符串是"1.0"而且為null ,哪個更大? JDK源代碼不處理比較器中的NullPointerExceptions:如果給它一個null,則自動裝箱將失敗。

最糟糕的是,我需要用其他三個類重復這個模式,這個類比較不同類型的字符串,並且在解析時可能引發其他三個異常。

究竟為什么我認為解析應該在比較器之外進行,異常處理在它到達你的代碼之前處理。

嘗試這個:

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 Strings和其他不可解析的字符串將會混合在一起。 這可能不是什么大不了的,考慮到它的好處 - 這給你一個保證正確的比較器,而使用手動編碼的比較器,即使是相對簡單的比較器,令人驚訝的是提交一個細微的錯誤是多么容易傳遞性,或者,反對稱性。

http://google-collections.googlecode.com

以下是我改進比較器的方法:

首先,提取一種轉換價值的方法。 這是重復的,多次嘗試......捕獲總是很難看 - >更好地擁有盡可能少的捕獲量。

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

......是的,這是一個分支嵌套的三元組。 如果有人抱怨它,請說出其他人在說什么: 處理空值不是比較者的工作。

似乎有兩個問題在這里混合,也許應該分解成單獨的組件。 考慮以下:

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。

如果您能夠更改簽名,我建議您編寫方法,以便它可以接受任何受支持的對象。

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

您甚至可以遞歸地將字符串轉換為雙打,然后返回使用這些對象調用自身的結果。

恕我直言,你應該首先創建一個從String返回Double的方法,嵌入null並解析失敗的情況(但你必須定義在這種情況下要做什么:拋出異常?返回默認值?)。

然后你的比較器只需比較獲得的Double實例。

換句話說,重構......

但我仍然想知道為什么你需要比較字符串,雖然期望它們代表雙打。 我的意思是,什么阻止你在實際使用這個比較器的代碼中操縱雙打?

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

}

我將該方法提取為一個抽象類,在其他情況下可以重用(盡管類名很糟糕)。

干杯。

所以我改進了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;  
 }
}

似乎你可能會發現更清晰的重命名測試t1retValq

至於重復模式...呃。 您可以使用帶有反射的泛型來調用適當的parseX方法。 雖然看起來不值得。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM