[英]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和其他不可解析的字符串將會混合在一起。 這可能不是什么大不了的,考慮到它的好處 - 這給你一個保證正確的比較器,而使用手動編碼的比較器,即使是相對簡單的比較器,令人驚訝的是提交一個細微的錯誤是多么容易傳遞性,或者,反對稱性。
以下是我改進比較器的方法:
首先,提取一種轉換價值的方法。 這是重復的,多次嘗試......捕獲總是很難看 - >更好地擁有盡可能少的捕獲量。
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;
}
}
似乎你可能會發現更清晰的重命名測試到t1和retVal到q 。
至於重復模式...呃。 您可以使用帶有反射的泛型來調用適當的parseX方法。 雖然看起來不值得。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.