作者:Jack47
轉載請保留作者和原文出處
歡迎關注我的微信公眾賬號程序員傑克,兩邊的文章會同步,也可以添加我的RSS訂閱源。
本文是我寫的Google開源的Java編程庫Guava系列之一,主要介紹Guava中提供的很多小工具類,這些類讓Java語言用起來更舒暢。
使用或者避免null值##
null引用的發明者Sir C.A.R.Hoare(也是快排算法的發明者)把null稱之為十億美元錯誤。Guava的開發者們通過研究Google的代碼發現95%的集合中都不需要支持為null的值,所以對於開發者而言,遇到null時需要快速失敗而不是默默地接受null。null的含義在大部分場景下都不夠清晰,例如Map.get(key)返回null時,可能是因為map中的值就是null,或者map中沒有這個key。但null在有些情況下也很有用,從內存和性能方面來看,null很廉價,在使用對象數組,是不可避免的要用到null的。綜合考慮之后,Guava庫中絕大部分工具都被設計成遇到null時快速失敗。
Optional
如果程序員需要使用null來表示不存在的情況,那么Optional<T>就能派上用場了。Optional<T>是用非null的值來代替一個可能為null的值。舉個例子:
Optional<Integer> possible = Optional.of(null);
boolean isPresent = possible.isPresent(); // returns false
再看這個例子:
Optional<Integer> possible = Optional.of(5);
boolean isPresent = possible.isPresent(); // returns true
possible.get(); // returns 5
聰明的讀者已經發現了,Optional是一個容器對象,它可能容納一個非null的值,也可能沒有值。如果這個值存在,isPresent()函數會返回true而且get()函數會返回這個值。
引入Optional類除了因為給null一個有意義的名字而增加了可讀性外,最大的好處是Optional能夠強制你主動思考程序中值不存在的情況,而null是很容易被忽略的。如果值不存在,想使用默認值,可以使用Optional中提供的T or(T)函數,例如:
Optional<Integer> default = Optional.of(0);
Optional possible = Optional.of(field);
return possible.or(default)
如果field為null,那么返回值就是default,調用者需要先判斷值是否存在isPresent(),如果存在,再拿到值get()。
Strings類提供了值可能為null的一些函數,例如:
boolean invalid = Strings.isNullOrEmpty(res);
String name = Strings.emptyToNull(passedName);
Preconditions(先決條件)##
Preconditions類中提供了幾個非常實用的靜態函數來幫助檢查調用函數或構造函數時的先決條件是否滿足。這些函數都接受一個boolean類型的表達式,如果表達式為false,會拋出一個非受檢異常,來通知調用方發生了調用錯誤。
Preconditions類的目的是提高代碼的可讀性,例如checkArgument(i >=0 , "Argument was %s but expected nonnegative", i);,一看就知道是在檢查傳入的參數;this.field = checkNotNull(field),是檢查field字段不為空。這里需要注意提供的錯誤信息需要清晰有效。
對象的通用函數##
使用這些函數能夠簡化實現對象函數的過程,例如hashCode()和toString()函數
equal函數###
如果對象內部變量可以為null,實現equal函數有些費勁,因為需要單獨檢查null。Objects.equal函數已經實現了對null敏感的檢查,不會出現NullPointerException的異常。
Objects.equal("a", "a"); //returns true
Objects.equal(null, "a"); // returns false
Objects.equal(null, null); // returns true
注:最新的JDK 7里引入了Ojbects類,提供了同樣的函數。
hashCode函數###
平常實現hashCode函數是不是很痛苦?如果類內部的成員變量較多,代碼就會比較冗長。Guava提供了Objects.hashCode(field1, field2,...,fieldn)的函數,它能夠對指定順序的多個字段進行哈希,這樣就不用自己手工實現一遍對各個字段進行哈希的過程了。舉個栗子:
return Objects.hashCode(name, address, url);
toString函數
toString在日志和調試中發揮巨大威力,但是實現起來很麻煩,需要注意各個有用字段輸出時的組織格式。來看看MoreObjects.toStringHelper如何讓整個過程變的簡單:
// return "MyObject{x=1,y=2}
Objects.toStringHelper(this)
.add("x", 1)
.add("y", 2)
.toString();
compare/compareTo函數
實現比較器(Comparator),或者實現Comparable接口時,通常需要對類內部的所有成員變量進行比較,實現起來很麻煩。Guava提供了ComparisonChain類,它能夠進行"懶"比較:只有當發現為0的結果,才會繼續后面的結果,不然就忽略后續的比較。舉個例子:
public int compareTo(Foo other) {
return ComparisonChain.start()
.compare(this.x, other.x)
.compare(this.y, other.y)
.result();
}
Ordering
Guava提供了一個流暢型(fluent)的比較器(Comparator)類:Ordering,它提供了鏈條函數來微調或者增強已有的比較器,或者構造復雜的比較器,應用到對象的集合上。
Ordering的核心是一個Comparator實例。使用已有的比較器來構造一個Ordering實例:
Ordering<Item> ordering = Ordering.from(Comparator<Item> comparator);
也可以使用自然順序: Ordering<T>.natural()
或者自己實現一個Ordering類,只需要繼承並實現compare()函數就可以。
對Ordering進行微調:
reverse()
compound(Comparator)
onResultOf(Function)
nullsFirst()
由於Ordering類繼承自Comparator,所以在任何需要Comparator的地方,都可以使用Ordering代替,同時Ordering提供了一些操作:
immutableSortedCopy()
isOrdered()/ isStrictlyOrdered()
min()
字符串相關的函數
合並(Joiner)
流暢風格的Joiner提供了使用分隔符把一些字符串序列合並到一起的功能。例如:
Joiner joiner = Joiner.on("; ").skipNulls();
// returns "Harry; Ron; Hermione
return joiner.join("Harry", null, "Ron", "Hermione");
如果不想跳過值為null的字符串,想用某個特定字符串代替null,可以使用函數useForNull(String)。
Joiner類也可以用在其他對象類型上,此時會使用對象的toString()函數得到字符串,然后進行合並。
切分(Splitter)
Java的字符串分割函數有一些詭異的行為,例如String.split()函數會默默地把尾部的分隔符丟棄掉。而使用Splitter的好處在於可以完整地顯示地控制這些行為。Splitter類可以用來在任意的Pattern, char, String或者CharMatcher上進行分割。舉個例子:
// returns ["foo", " bar", "", " qux", ""]
Splitter.on(',').split("foo, bar,, qux,");
Splitter還提供了其他的配置函數來對合並過程進行定制:omitEmptyStrings(),trimResults(), limit()等。例如對於上面的例子,如果想忽略空字符串,讓結果中去掉開頭和結尾的空格,可以這樣做:
// returns ["foo","bar","qux"]
Splitter.on(',')
.trimReults()
.omitEmptyStrings()
.split("foo, bar,, qux,");
注意:Splitter和Joiner實例都是不可變的(immutable),所以Splitter和Joiner都是線程安全的,可以聲明為static final的常量來使用。他們的配置函數都是返回一個新的Splitter實例,此時需要使用返回的新的Splitter的實例。
字符匹配(Character Matchers)
CharMatcher類的設計思想很巧妙,定義兩個基本屬性,然后任意組合他們。這樣API的復雜度是線性增加的,但是靈活性和功能是平方式增強的。
CharMatcher定義的兩個屬性:
- 需要匹配什么樣的字符串?
- 在這些匹配的字符串上執行什么樣的操作?例如
trimming,collapsing,removing等。
一些例子:
// remove control characters
String noControl = CharMatcher.JAVA_ISO_CONTROL.removeFrom(inputString);
// only the digits
String theDigits = CharMatcher.DIGIT.retainFrom(inputString);
// eliminate all characters that aren't digits or lowercase
String lowerAndDigit = CharMatcher.JAVA_DIGIT.or(CharMatcher.JAVA_LOWER_CASE).retainFrom(inputString);
// trim whitespace and replace/collapse whitespace into single spaces
String spaced = CharMatcher.WHITESPACE.trimAndCollapseFrom(inputString, ' ');
認真的讀者通過看上面的例子會發現CharMatcher已經提供了很多現成的匹配特定字符串的常量,例如WHITESPACE,JAVA_DIGIT等。也可以通過其他幾個函數來構造匹配特定字符串的CharMatcher:
is(char);
isNot(char);
negate()
inRange(char, char)
or(CharMatcher);
and(CharMatcher);
可以在CharMatcher上執行的操作
boolean matchesAllOf(CharSequence)
boolean matchesAnyOf(CharSequence)
boolean matchesNoneOf(CharSequence)
int indexIn(CharSequence, int)
int countIn(CharSequence)
String removeFrom(CharSequence)
String retainFrom(CharSequence)
String trimFrom(CharSequence)
String replaceFrom(CharSequence, char)
Charsets
Charsets類提供了Java平台的所有實現中都支持的六個標准的字符集的常量引用。所以不要這樣做:
try {
bytes = content.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
throw new AssertionError(e);
}
而是用下面的代碼替代:
bytes = content.getBytes(Charsets.UTF_8);
注:JDK7中StandardCharsets類已經實現了同樣功能
基本類型相關的函數
Java中Arrays類提供了眾多對數組進行操作的函數,基礎類型對應的包裝類例如Integer,也提供了很多使用函數。而Guava為Java中的8個基本類型提供了其他一些非常實用的函數,例如數組和集合相關的API,從類型轉換到byte數組的表示方式等。
例如:
int[] content = {1,3,4};
Ints.indexOf(content, 3); // 1
Ints.concat(new int[] {1,2}, new int[]{3, 4}) // 1,2,3,4
Ints.contains(new int[]{10,20,30,40}, 20) // true
Ints.min(10,20,30,40) // 10
byte[] bytes = Ints.toByteArray(integer);
回到本系列目錄: Google Java編程庫Guava介紹
如果您看了本篇博客,覺得對您有所收獲,請點擊右下角的“推薦”,讓更多人看到!
