瓜娃系列 (6) - ComparisonChain和primitives包

有時候咱們不可避免地要實現Comparator, 好作排序之類的事情.

要比較兩個整數的時候, 我一度曾經這麼寫:

return a - b;


多簡單啊! 若是a比b大, 無疑這個東西返回正數了.

惋惜啊, 現實永遠比理想殘酷. java的整數不是數學中的整數, 它可能溢出地!
int a = -2000000000;
int b =  2000000000;
System.out.println(a - b);
// prints "294967296"


正確的寫法是:
if (a > b) {
  return 1;
} else if (a < b) {
  return -1;
} else {
  return 0;
}

可是, 太麻煩了哇! 好吧, 好吧, 我知道java是一門羅唆的語言藝術, 講究如何如何啥的, 但是, 但是, 太麻煩了哇! 太麻煩了哇!

在guava裏, 對全部原始類型都提供了比較的工具函數來避免這個麻煩. 好比對long, 能夠用Longs.compare():
return Longs.compare(a, b);


其它, 天然還有Ints, Shorts, Floats, Doubles等等, 就不騙字數了.

下面看一個簡單的model類:
class Person {
  final String firstName;
  final String lastName;
  final int age;
}


下面我來實現一個Comparator, 按照名字而後年齡排序:
class PersonComparator implements Comparator<Person> {
  @Override public int compare(Person p1, Person p2) {
    int result = p1.firstName.compareTo(p2.firstName);
    if (result != 0) {
      return result;
    }
    result = p1.lastName.compareTo(p2.lastName);
    if (result != 0) {
      return result;
    }
    return Ints.compare(p1.age, p2.age);
  }
}

算中規中矩吧? 嗯, 就是以爲有點羅唆 (好啦, 好啦, "java是一門羅唆的語言藝術", 你好羅唆啊!). 要是能直接就說: 按firstName, lastName, age比較就行了.

有一種作法是把這些東西存到一個List<Comparable>而後用一個Comparator<List>來比較:
Ordering.natural().lexicographical().compare(
    Arrays.asList(p1.firstName, p1.lastName, p1.age),
    Arrays.asList(p2.firstName, p2.lastName, p2.age));


可是這個東西有點步驟過多, 並且, 自動box那個int, 以及建立兩個臨時List對象, 都彷佛有點過了, 畢竟, Comparator每每是被調用屢次來排序不少對象的.

對此, guava有一個至關聰明的解決辦法, 用 ComparisonChain:
class PersonComparator implements Comparator<Person> {
  @Override public int compare(Person p1, Person p2) {
    return ComparisonChain.start()
        .compare(p1.firstName, p2.firstName)
        .compare(p1.lastName, p2.lastName)
        .compare(p1.age, p2.age)
        .result();
  }
}


這個東西的原理哪, 就是利用多態, 當p1.firstName比p2.firstName大的時候, 後續的compare()函數都是空的, 直接返回, 儘可能節省計算.
另外, 由於它對全部原始類型都作了重載, 因此也不會付裝箱的代價.

(我的意見, 不表明組織承認: 這個start()函數有點彆扭. ComparisonChain應該提供靜態compare()方法, 這樣客戶端就能夠省去那個討厭的start())


對了, 剛纔在例子中我實在忍不住引用了 Ordering類. 要說這個類不是作了多少了不起的事情, 它的好處是相關的功能都在一個類裏面, 好找 (點一下ctrl-space, IDE的自動提示就夠用了). 比較經常使用的幾個函數:
  • natural(): 比較兩個Comparable.
  • reverse(): 把當前ordering反過來, 大的變小, 小的變大.
  • compound(): 若是當前ordering比較結果是平局, 用另一個Comparator作加時賽.
  • nullsFirst(): 把null看成最小的, 排在前面.
  • nullsLast(): null最大.
  • binarySearch(): 根據當前ordering在排序列表裏二分查找.


好比, 上面若是我lastName可能爲null, 而後我要把null列到後面, 我就能夠寫:

class PersonOrdering extends Ordering<Person> {
  @Override public int compare(Person p1, Person p2) {
    return ComparisonChain.start()
        .compare(p1.firstName, p2.firstName)
        .compare(p1.lastName, p2.lastName, Ordering.<Person>natural().nullsLast())
        .compare(p1.age, p2.age)
        .result();
  }
}
這裏, 既然我已經用Ordering了, 我就順手牽羊把PersonComparator變成PersonOrdering了.
相關文章
相關標籤/搜索