原文出處:http://blog.csdn.net/baoq_v5_java/article/details/45023617
Guava工程包含了若干被google的java項目廣泛依賴的核心庫,例如:集合 [collections] 、緩存 [caching] 、原生類型支持 [primitives support] 、併發庫 [concurrency libraries] 、通用註解 [commonannotations] 、字符串處理 [string processing] 、I/O 等等。所有這些工具每天都在被Google的工程師應用在產品服務中。
這些高質量的API可以使你的java代碼更加優雅,更加簡潔,讓你的工作更加輕鬆愉悅,下面我們來開啓Java編程學習之旅。
源碼包的簡單說明:
com.google.common.annotations:普通註解類型。
com.google.common.base:基本工具類庫和接口。
com.google.common.cache:緩存工具包,非常簡單易用且功能強大的JVM內緩存。
com.google.common.collect:帶泛型的集合接口擴展和實現,以及工具類,這裏你會發現很多好玩的集合。
com.google.common.eventbus:發佈訂閱風格的事件總線。
com.google.common.hash: 哈希工具包。
com.google.common.io:I/O工具包。
com.google.common.math:原始算術類型和超大數的運算工具包。
com.google.common.net:網絡工具包。
com.google.common.primitives:八種原始類型和無符號類型的靜態工具包。
com.google.common.reflect:反射工具包。
com.google.common.util.concurrent:多線程工具包。
1. 基本工具(Basic utilities)
1) 使用和避免null(Optional)
null會引起歧義,會造成讓人迷惑的錯誤,有時也會讓人感到不爽。Guava中的許多工具遇到null時,會拒絕或者馬上報錯,而不是盲目的接受。
鑑於此google的guava庫中提供了Optional接口來使null快速失敗,即在可能爲null的對象上做了一層封裝,在使用Optional靜態方法of時,如果傳入的參數爲null就拋出NullPointerException異常。
在Guava中Optional類就是用來強制提醒程序員,注意對Null的判斷。
Optional的另外幾個方法
Optional<T>.of(T) 爲Optional賦值,當T爲Null直接拋NullPointException,建議這個方法在調用的時候直接傳常量,不要傳變量
Optional<T>.fromNullable(T) 爲Optional賦值,當T爲Null則使用默認值。建議與or方法一起用,風騷無比
Optional<T>.absent() 爲Optional賦值,採用默認值
T or(T) 當Optional的值爲null時,使用or賦予的值返回。與fromNullable是一對好基友
T get() 當Optional的值爲null時,拋出IllegalStateException,返回Optional的值
boolean isPresent() 如果Optional存在值,則返回True
T orNull() 當Optional的值爲null時,則返回Null。否則返回Optional的值
Set<T> asSet() 將Optional中的值轉爲一個Set返回,當然只有一個值啦,或者爲空,當值爲null時。
使用Optional的意義?
使用Optional除了賦予null語義,增加了可讀性,最大的優點在於它一種傻瓜式的防護,Optional迫使你積極思考引用缺失的情況,因爲你必須顯式地從Optional獲取引用。直接使用null很容易讓人忘掉某些情形,儘管FindBugs可以幫助查找null相關的問題,但是我們還是認爲它並不能準確地定位問題根源。
2) 前提條件(Preconditions)
使方法的條件檢查更簡單。
Guava在Preconditions類中提供了若干前置條件判斷的實用方法,我們強烈建議在Eclipse中靜態導入這些方法。每個方法都有三個變種:
a) 沒有額外參數:拋出的異常中沒有錯誤消息;
b) 有一個Object對象作爲額外參數:拋出的異常使用Object.toString() 作爲錯誤消息;
c) 有一個String對象作爲額外參數,並且有一組任意數量的附加Object對象:這個變種處理異常消息的方式有點類似printf,但考慮GWT的兼容性和效率,只支持%s指示符。
例如:查看源代碼打印幫助
checkArgument(i>= 0, "Argument was %s but expected nonnegative", i);
checkArgument(i< j, "Expected i < j, but %s > %s", i, j);
方法聲明(不包括額外參數) |
描述 |
檢查失敗時拋出的異常 |
checkArgument(boolean) |
檢查boolean是否爲true,用來檢查傳遞給方法的參數。 |
IllegalArgumentException |
檢查value是否爲null,該方法直接返回value,因此可以內嵌使用checkNotNull。 |
NullPointerException |
|
用來檢查對象的某些狀態。 |
IllegalStateException |
|
檢查index作爲索引值對某個列表、字符串或數組是否有效。index>=0 && index<size * |
IndexOutOfBoundsException |
|
檢查index作爲位置值對某個列表、字符串或數組是否有效。index>=0 && index<=size * |
IndexOutOfBoundsException |
|
checkPositionIndexes(int start, int end, int size) |
檢查[start, end]表示的位置範圍對某個列表、字符串或數組是否有效* |
IndexOutOfBoundsException |
3) 常見的對象方法(Objects)
簡化Object方法實現,如hashCode()和toString();
a) equals
當一個對象中的字段可以爲null時,實現Object.equals方法會很痛苦,因爲不得不分別對它們進行null檢查。使用Objects.equal幫助你執行null敏感的equals判斷,從而避免拋出NullPointerException。
b) hashCode
用對象的所有字段作散列[hash]運算應當更簡單。Guava的Objects.hashCode(Object...)會對傳入的字段序列計算出合理的、順序敏感的散列值。你可以使用Objects.hashCode(field1, field2, …, fieldn)來代替手動計算散列值。
c) toString
好的toString方法在調試時是無價之寶,但是編寫toString方法有時候卻很痛苦。使用 Objects.toStringHelper可以輕鬆編寫有用的toString方法。
4) 排序
Guava強大的”流暢風格比較器”,具體到下章會介紹到。
5) Throwable類
簡化了異常和錯誤的傳播與檢查;
guava類庫中的Throwables提供了一些異常處理的靜態方法,這些方法的從功能上分爲兩類,一類是幫你拋出異常,另外一類是幫你處理異常。
RuntimeException propagate(Throwable) |
如果Throwable是Error或RuntimeException,直接拋出;否則把Throwable包裝成RuntimeException拋出。返回類型是RuntimeException,所以你可以像上面說的那樣寫成throw Throwables.propagate(t),Java編譯器會意識到這行代碼保證拋出異常。 |
void propagateIfInstanceOf( Throwable, Class<X extends Exception>) throws X |
Throwable類型爲X才拋出 |
void propagateIfPossible( Throwable) |
Throwable類型爲Error或RuntimeException才拋出 |
void propagateIfPossible( Throwable, Class<X extends Throwable>) throws X |
Throwable類型爲X, Error或RuntimeException才拋出 |
2. 集合(Collections)
介紹guava對jdk集合類的擴展,包括不可變集合,新集合類型: multisets, multimaps, tables, bidirectional maps等,強大的集合工具類: 提供java.util.Collections中沒有的集合工具,擴展工具類:讓實現和擴展集合類變得更容易,比如創建Collection的裝飾器,或實現迭代器
集合API的使用, 可以簡化集合的創建和初始化;
guava API 提供了有用的新的集合類型,協同已經存在的java集合工作的很好。
分別是 MultiMap, MultiSet, Table, BiMap,ClassToInstanceMap
1) google guava的不可變集合
不可變對象有很多優點:
a) 當對象被不可信的庫調用時,不可變形式是安全的。
b) 當不可變對象被對個線程調用時,不存在競態條件問題;
c) 不可變集合不需要考慮變化,因此可以節約時間和空間,所有不可變集合都比可變集合形式有更好的內存利用率(分析和測試細節);
d) 不可變對象因爲有固定不變,可以用作常量來安全使用。
總結:數據不可變;不需要同步邏輯;線程安全;自由共享;容易設計和實現;內存和時間高效
創建對象的不可拷貝是一項很好的防禦性編程技巧,Guava爲所有JDK標準集合類型和Guava新集合類型都提供了簡單易用的不可變版本。
JDK也提供了可以將集合變成不可變的方法,Collections.unmodifiableXXX,但是被認爲是不好的。
a) 笨重而且累贅:不能舒適地用在所有想做防禦性拷貝的場景;
b) 不安全:要保證沒人通過原集合的引用進行修改,返回的集合纔是事實上不可變的;
c) 低效:包裝過的集合仍然保有可變集合的開銷,比如併發修改的檢查、散列表的額外空間,等等。
提示:guava不可變集合的實現都不接受null值,經過對google內部代碼的研究發現,google內部只有不超過5%的情況下集合中允許了null值,其他情況下都不允許。如果我們相用null的不可變集合,那我們就用jdk中的集合類進行操作,然後進行集合工具類的處理Collections.unmodifiableXXX。
細節:關聯可變集合和不可變集合
可變集合接口 |
屬於jdk還是guava |
不可變版本 |
Collection |
JDK |
ImmutableCollection |
List |
JDK |
ImmutableList |
Set |
JDK |
ImmutableSet |
SortedSet/NavigableSet |
JDK |
ImmutableSortedSet |
Map |
JDK |
ImmutableMap |
SortedMultiset |
Guava |
ImmutableSortedMultiset |
Multimap |
Guava |
ImmutableMultimap |
ListMultimap |
Guava |
ImmutableListMultimap |
SetMultimap |
Guava |
ImmutableSetMultimap |
BiMap |
Guava |
ImmutableBiMap |
ClassToInstanceMap |
Guava |
ImmutableClassToInstanceMap |
Table |
Guava |
ImmutableTable |
2) google guava集合之Multiset
Multiset看似是一個Set,但是實質上它不是一個Set,它沒有繼承Set接口,它繼承的是Collection<E>接口,你可以向Multiset中添加重複的元素,Multiset會對添加的元素做一個計數。
它本質上是一個Set加一個元素計數器。
顯然計數不是問題,Multiset還提供了add和remove的重載方法,可以在add或這remove的同時指定計數的值。
常用實現 Multiset 接口的類有:
HashMultiset: 元素存放於 HashMap
LinkedHashMultiset:元素存放於 LinkedHashMap,即元素的排列順序由第一次放入的順序決定
TreeMultiset:元素被排序存放於TreeMap
EnumMultiset: 元素必須是 enum 類型
ImmutableMultiset:不可修改的 Mutiset
看到這裏你可能已經發現 GuavaCollections 都是以 create 或是 of 這樣的靜態方法來構造對象。這是因爲這些集合類大多有多個參數的私有構造方法,由於參數數目很多,客戶代碼程序員使用起來就很不方便。而且以這種方式可以返回原類型的子類型對象。另外,對於創建範型對象來講,這種方式更加簡潔。
3) google guava的BiMap:雙向Map
我們知道Map是一種鍵值對映射,這個映射是鍵到值的映射,而BiMap首先也是一種Map,他的特別之處在於,既提供鍵到值的映射,也提供值到鍵的映射,所以它是雙向Map.
BiMap的常用實現有:
HashBiMap: key 集合與 value 集合都有 HashMap 實現
EnumBiMap: key 與 value 都必須是 enum 類型
ImmutableBiMap: 不可修改的 BiMap
4) google guava的Multimaps:一鍵多值的Map
有時候我們需要這樣的數據類型Map<String,Collection<String>>,guava中的Multimap就是爲了解決這類問題的。
Multimap提供了豐富的實現,所以你可以用它來替代程序裏的Map<K, Collection<V>>,具體的實現如下:
實現 |
Key實現 |
Value實現 |
ArrayListMultimap |
HashMap |
ArrayList |
HashMultimap |
HashMap |
HashSet |
LinkedListMultimap |
LinkedHashMap |
LinkedList |
LinkedHashMultimap |
LinkedHashMap |
LinkedHashSet |
TreeMultimap |
TreeMap |
TreeSet |
ImmutableListMultimap |
ImmutableMap |
ImmutableList |
ImmutableSetMultimap |
ImmutableMap |
ImmutableSet |
5) google guava集合之Table
在guava庫中還提供了一種二維表結構:Table。使用Table可以實現二維矩陣的數據結構,可以是稀溜矩陣。
通常來說,當你想使用多個鍵做索引的時候,你可能會用類似Map<FirstName, Map<LastName, Person>>的實現,這種方式很醜陋,使用上也不友好。Guava爲此提供了新集合類型Table,它有兩個支持所有類型的鍵:”行”和”列”。Table提供多種視圖,以便你從各種角度使用它:
rowMap():用Map<R, Map<C, V>>表現Table<R, C,V>。同樣的, rowKeySet()返回”行”的集合Set<R>。
row(r) :用Map<C, V>返回給定”行”的所有列,對這個map進行的寫操作也將寫入Table中。
類似的列訪問方法:columnMap()、columnKeySet()、column(c)。(基於列的訪問會比基於的行訪問稍微低效點)
cellSet():用元素類型爲Table.Cell<R, C, V>的Set表現Table<R,C, V>。Cell類似於Map.Entry,但它是用行和列兩個鍵區分的。
6) Guava集合:使用Iterators簡化Iterator操作
Iterators是Guava中對Iterator迭代器操作的幫助類,這個類提供了很多有用的方法來簡化Iterator的操作。
7) ClassToInstanceMap可以實現map的key值是多個類型
有的時候,你的map的key並不是一種類型,他們是很多類型,你想通過映射他們得到這種類型,guava提供了ClassToInstanceMap滿足了這個目的。
除了繼承自Map接口,ClassToInstaceMap提供了方法 TgetInstance(Class<T>) 和 T putInstance(Class<T>, T),消除了強制類型轉換。
8) Ordering犀利的比較器
Ordering是Guava類庫提供的一個犀利強大的比較器工具,Guava的Ordering和JDK Comparator相比功能更強。它非常容易擴展,可以輕鬆構造複雜的comparator,然後用在容器的比較、排序等操作中。
本質上來說,Ordering實例無非就是一個特殊的Comparator 實例。Ordering只是需要依賴於一個比較器(例如,Collections.max)的方法,並使其可作爲實例方法。另外,Ordering提供了鏈式方法調用和加強現有的比較器。
常見的靜態方法:
natural():使用Comparable類型的自然順序,例如:整數從小到大,字符串是按字典順序;
usingToString() :使用toString()返回的字符串按字典順序進行排序;
arbitrary() :返回一個所有對象的任意順序,即compare(a, b) == 0 就是 a == b (identity equality)。 本身的排序是沒有任何含義, 但是在VM的生命週期是一個常量。
9) ComparisonChain比較
實現一個比較器[Comparator],或者直接實現Comparable接口有時也傷不起。考慮一下這種情況:
class Studentimplements Comparable<Student>{
private String lastName;
private String firstName;
private int zipCode;
//jdk
public int compareTo(Student other) {
int cmp =lastName.compareTo(other.lastName);
if (cmp != 0) {
return cmp;
}
cmp = firstName.compareTo(other.firstName);
if (cmp != 0) {
return cmp;
}
return Integer.compare(zipCode, other.zipCode);
}
}
這部分代碼太瑣碎了,因此很容易搞亂,也很難調試。我們應該能把這種代碼變得更優雅,爲此,Guava提供了ComparisonChain。
ComparisonChain執行一種懶比較:它執行比較操作直至發現非零的結果,在那之後的比較輸入將被忽略。
//guava比較優雅
public int compareTo(Student other) {
returnComparisonChain.start()
.compare(this.lastName,other.lastName)
.compare(this.firstName,other.firstName)
.compare(this.zipCode,other.zipCode, Ordering.natural().nullsLast())
.result();
}
這種Fluent接口風格的可讀性更高,發生錯誤編碼的機率更小,並且能避免做不必要的工作。
3. 緩存(Caches)
google guava框架提供了內存緩存的功能,可以很方便的緩存對象,設置生命週期, 及緩存對象的弱引用強應用 軟引用等
1) 使用google guava做內存緩存
google guava中有cache包,此包提供內存緩存功能。內存緩存需要考慮很多問題,包括併發問題,緩存失效機制,內存不夠用時緩存釋放,緩存的命中率,緩存的移除等等。當然這些東西guava都考慮到了。
guava的內存緩存非常強大,可以設置各種選項,而且很輕量,使用方便。另外還提供了下面一些方法,來方便各種需要:
ImmutableMap<K, V> getAllPresent(Iterable<?> keys) 一次獲得多個鍵的緩存值
put和putAll方法向緩存中添加一個或者多個緩存項
invalidate 和 invalidateAll方法從緩存中移除緩存項
asMap()方法獲得緩存數據的ConcurrentMap<K, V>快照
cleanUp()清空緩存
refresh(Key) 刷新緩存,即重新取緩存數據,更新緩存
2) google guava緩存分析
guava緩存過期時間分爲兩種,一種是從寫入時開始計時,一種是從最後訪問時間開始計時,而且guava緩存的過期時間是設置到整個一組緩存上的;這和EHCache,redis,memcached等不同,這些緩存系統設置都將緩存時間設置到了單個緩存上。
guava緩存設計成了一組對象一個緩存實例,這樣做的好處是一組對象設置一組緩存策略,你可以根據不同的業務來設置不同的緩存策略,包括弱引用,軟引用,過期時間,最大項數等。另外一點好處是你可以根據不同的組來統計緩存的命中率,這樣更有意義一些。
這樣做也是有缺點的,缺點是首先是每個緩存組都需要聲明不同的緩存實例,具體到業務程序中可能就是每個業務對象一個緩存了。這樣就把不同的業務緩存分散到不同的業務系統中了,不太好管理。
4. 函數式風格(Functional idioms)
5. 併發(Concurrency)
併發編程是一個難題,但是一個強大而簡單的抽象可以顯著的簡化併發的編寫。出於這樣的考慮,Guava 定義了 ListenableFuture接口並繼承了JDK concurrent包下的Future 接口。
1) Guava併發:ListenableFuture使用介紹以及示例
ListenableFuture顧名思義就是可以監聽的Future,它是對java原生Future的擴展增強,本文介紹ListenableFuture的用法和擴展實現
ListenableFuture顧名思義就是可以監聽的Future,它是對java原生Future的擴展增強。我們知道Future表示一個異步計算任務,當任務完成時可以得到計算結果。如果我們希望一旦計算完成就拿到結果展示給用戶或者做另外的計算,就必須使用另一個線程不斷的查詢計算狀態。這樣做,代碼複雜,而且效率低下。使用ListenableFuture Guava幫我們檢測Future是否完成了,如果完成就自動調用回調函數,這樣可以減少併發程序的複雜度。
另外ListenableFuture還有其他幾種內置實現:
SettableFuture:不需要實現一個方法來計算返回值,而只需要返回一個固定值來做爲返回值,可以通過程序設置此Future的返回值或者異常信息
CheckedFuture:這是一個繼承自ListenableFuture接口,他提供了checkedGet()方法,此方法在Future執行發生異常時,可以拋出指定類型的異常。
2) Guava併發:RateLimiter限制資源的併發訪問線程數
RateLimiter類似於JDK的信號量Semphore,他用來限制對資源併發訪問的線程數
RateLimiter類似於JDK的信號量Semphore,他用來限制對資源併發訪問的線程數。
RateLimiterlimiter = RateLimiter.create(4.0);//每秒不超過4個任務被提交
limiter.acquire(); //請求RateLimiter,超過permits會被阻塞
executor.submit(runnable);//提交任務
也可以以非阻塞的形式來使用:
If(limiter.tryAcquire()){ //未請求到limiter則立即返回false
doSomething();
}else{
doSomethingElse();
}
3) Guava併發:使用Monitor控制併發
Monitor就像java原生的synchronized,ReentrantLock一樣,每次只允許一個線程執行代碼塊,且可重佔用,每一次佔用要對應一次退出佔用。
/**
* 通過Monitor的Guard進行條件阻塞
*/
public classMonitorSample {
private List<String> list = new ArrayList<String>();
private static final int MAX_SIZE = 10;
private Monitor monitor = new Monitor();
private Monitor.Guard listBelowCapacity = new Monitor.Guard(monitor) {
@Override
public boolean isSatisfied() {
return list.size() < MAX_SIZE;
}
};
public void addToList(String item) throws InterruptedException {
monitor.enterWhen(listBelowCapacity); //Guard(形如Condition),不滿足則阻塞,而且我們並沒有在Guard進行任何通知操作
try {
list.add(item);
} finally {
monitor.leave();
}
}
}
就如上面,我們通過if條件來判斷是否可進入Monitor代碼塊,並再try/finally中釋放:
if(monitor.enterIf(guardCondition)) {
try {
doWork();
}finally {
monitor.leave();
}
}
其他的Monitor訪問方法:
Monitor.enter //進入Monitor塊,將阻塞其他線程直到Monitor.leave
Monitor.tryEnter//嘗試進入Monitor塊,true表示可以進入, false表示不能,並且不會一直阻塞
Monitor.tryEnterIf//根據條件嘗試進入Monitor塊
這幾個方法都有對應的超時設置版本。
6. 字符串處理(Strings)
1) 連接器(Joiner)
用分隔符把字符串序列連接起來也可能會遇上不必要的麻煩。如果字符串序列中含有null,那連接操作會更難。Fluent風格的Joiner讓連接字符串更簡單。
警告:joiner實例總是不可變的。用來定義joiner目標語義的配置方法總會返回一個新的joiner實例。這使得joiner實例都是線程安全的,你可以將其定義爲staticfinal常量。
2) 拆分器(Splitter)
JDK內建的字符串拆分工具有一些古怪的特性。比如,String.split悄悄丟棄了尾部的分隔符。例如:
”,a,,b,”.split(“,”) //””, “a”, “”, “b” 只有尾部的空字符串被忽略了
Splitter使用令人放心的、直白的流暢API模式對這些混亂的特性作了完全的掌控。
a) 拆分器工廠:
方法 |
描述 |
Splitter.on(char) |
按單個字符拆分 |
Splitter.on(CharMatcher) |
按字符匹配器拆分 |
Splitter.on(String) |
按字符串拆分 |
Splitter.on(Pattern) Splitter.onPattern(String) |
按正則表達式拆分 |
Splitter.fixedLength(int) |
按固定長度拆分;最後一段可能比給定長度短,但不會爲空。 |
b) 拆分器修飾符:
方法 |
描述 |
omitEmptyStrings() |
從結果中自動忽略空字符串 |
trimResults() |
移除結果字符串的前導空白和尾部空白 |
trimResults(CharMatcher) |
給定匹配器,移除結果字符串的前導匹配字符和尾部匹配字符 |
limit(int) |
限制拆分出的字符串數量 |
如果你想要拆分器返回List,只要使用Lists.newArrayList(splitter.split(string))或類似方法。 警告:splitter實例總是不可變的。用來定義splitter目標語義的配置方法總會返回一個新的splitter實例。這使得splitter實例都是線程安全的,你可以將其定義爲static final常量。
3) 字符匹配器(CharMatcher)
然而使用CharMatcher的好處更在於它提供了一系列方法,讓你對字符作特定類型的操作:修剪[trim]、摺疊[collapse]、移除[remove]、保留[retain]等等。CharMatcher實例首先代表概念1:怎麼纔算匹配字符?然後它還提供了很多操作概念2:如何處理這些匹配字符?這樣的設計使得API複雜度的線性增加可以帶來靈活性和功能兩方面的增長。
注:CharMatcher只處理char類型代表的字符;它不能理解0x10000到0x10FFFF的Unicode增補字符。這些邏輯字符以代理對[surrogatepairs]的形式編碼進字符串,而CharMatcher只能將這種邏輯字符看待成兩個獨立的字符。
4) 字符集(Charsets)
Charsets針對所有Java平臺都要保證支持的六種字符集提供了常量引用。嘗試使用這些常量,而不是通過名稱獲取字符集實例。
5) 大小寫格式(CaseFormat)
CaseFormat被用來方便地在各種ASCII大小寫規範間轉換字符串——比如,編程語言的命名規範。CaseFormat支持的格式如下:
格式 |
範例 |
LOWER_CAMEL |
lowerCamel |
LOWER_HYPHEN |
lower-hyphen |
LOWER_UNDERSCORE |
lower_underscore |
UPPER_CAMEL |
UpperCamel |
UPPER_UNDERSCORE |
UPPER_UNDERSCORE |
CaseFormat的用法很直接:
CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL,"CONSTANT_NAME"
)); // returns "constantName"
我們CaseFormat在某些時候尤其有用,比如編寫代碼生成器的時候。
7. 原生類型(Primitives)
Java的原生類型也稱原始類型,也是基本數據類型byte、short、int、long、float、double、char和boolean。
在從Guava查找原生類型方法之前,可以先查查Arrays類,或者對應的基礎類型包裝類,如Integer。
原生類型不能當作對象或泛型的類型參數使用,這意味着許多通用方法都不能應用於它們。Guava提供了若干通用工具,包括原生類型數組與集合API的交互,原生類型和字節數組的相互轉換,以及對某些原生類型的無符號形式的支持。
原生類型 |
Guava工具類(都在com.google.common.primitives包) |
byte |
Bytes, SignedBytes, UnsignedBytes |
short |
Shorts |
int |
Ints, UnsignedInteger, UnsignedInts |
long |
Longs, UnsignedLong, UnsignedLongs |
float |
Floats |
double |
Doubles |
char |
Chars |
boolean |
Booleans |
Bytes工具類沒有定義任何區分有符號和無符號字節的方法,而是把它們都放到了SignedBytes和UnsignedBytes工具類中,因爲字節類型的符號性比起其它類型要略微含糊一些。
int和long的無符號形式方法在UnsignedInts和UnsignedLongs類中,但由於這兩個類型的大多數用法都是有符號的,Ints和Longs類按照有符號形式處理方法的輸入參數。
此外,Guava爲int和long的無符號形式提供了包裝類,即UnsignedInteger和UnsignedLong,以幫助你使用類型系統,以極小的性能消耗對有符號和無符號值進行強制轉換。
原生類型數組工具:
方法簽名 |
描述 |
List<Wrapper> asList(prim… backingArray) |
把數組轉爲相應包裝類的List |
prim[] toArray(Collection<Wrapper> collection) |
把集合拷貝爲數組,和collection.toArray()一樣線程安全 |
prim[] concat(prim[]… arrays) |
串聯多個原生類型數組 |
boolean contains(prim[] array, prim target) |
判斷原生類型數組是否包含給定值 |
int indexOf(prim[] array, prim target) |
給定值在數組中首次出現處的索引,若不包含此值返回-1 |
int lastIndexOf(prim[] array, prim target) |
給定值在數組最後出現的索引,若不包含此值返回-1 |
prim min(prim… array) |
數組中最小的值 |
prim max(prim… array) |
數組中最大的值 |
String join(String separator, prim… array) |
把數組用給定分隔符連接爲字符串 |
Comparator<prim[]> lexicographicalComparator() |
按字典序比較原生類型數組的Comparator |
*符號無關方法存在於Bytes, Shorts, Ints, Longs, Floats, Doubles, Chars, Booleans。而UnsignedInts,UnsignedLongs, SignedBytes, 或UnsignedBytes不存在。
*符號相關方法存在於SignedBytes, UnsignedBytes, Shorts, Ints, Longs, Floats, Doubles,Chars, Booleans, UnsignedInts, UnsignedLongs。而Bytes不存在。
通用工具方法:
方法簽名 |
描述 |
int compare(prim a, prim b) |
傳統的Comparator.compare方法,但針對原生類型。JDK7的原生類型包裝類也提供這樣的方法 |
prim checkedCast(long value) |
把給定long值轉爲某一原生類型,若給定值不符合該原生類型,則拋出IllegalArgumentException |
prim saturatedCast(long value) |
把給定long值轉爲某一原生類型,若給定值不符合則使用最接近的原生類型值 |
*這裏的整型包括byte, short, int, long。不包括char, boolean, float, 或double。
字節轉換方法:
Guava提供了若干方法,用來把原生類型按大字節序與字節數組相互轉換。所有這些方法都是符號無關的,此外Booleans沒有提供任何下面的方法。
方法簽名或字段簽名 |
描述 |
int BYTES |
常量:表示該原生類型需要的字節數 |
prim fromByteArray(byte[] bytes) |
使用字節數組的前Prims.BYTES個字節,按大字節序返回原生類型值;如果bytes.length <= Prims.BYTES,拋出IAE |
prim fromBytes(byte b1, …, byte bk) |
接受Prims.BYTES個字節參數,按大字節序返回原生類型值 |
byte[] toByteArray(prim value) |
按大字節序返回value的字節數組 |
8. 區間(Ranges)
1) 簡介
區間,有時也稱爲範圍,是特定域中的凸性(非正式說法爲連續的或不中斷的)部分。在形式上,凸性表示對a<=b<=c, range.contains(a)且range.contains(c)意味着range.contains(b)。
區間可以延伸至無限——例如,範圍”x>3″包括任意大於3的值——也可以被限制爲有限,如” 2<=x<5″。Guava用更緊湊的方法表示範圍,有數學背景的程序員對此是耳熟能詳的:
(a..b) = {x | a < x < b}
[a..b] = {x | a <= x <= b}
[a..b) = {x | a <= x < b}
(a..b] = {x | a < x <= b}
(a..+∞) = {x | x > a}
[a..+∞) = {x | x >= a}
(-∞..b) = {x | x < b}
(-∞..b] = {x | x <= b}
(-∞..+∞) = 所有值
上面的a、b稱爲端點 。爲了提高一致性,Guava中的Range要求上端點不能小於下端點。上下端點有可能是相等的,但要求區間是閉區間或半開半閉區間(至少有一個端點是包含在區間中的):
[a..a]:單元素區間
[a..a); (a..a]:空區間,但它們是有效的
(a..a):無效區間
Guava用類型Range<C>表示區間。所有區間實現都是不可變類型。
2) 構建區間
區間實例可以由Range類的靜態方法獲取:
(a..b) |
open(C, C) |
[a..b] |
closed(C, C) |
[a..b) |
closedOpen(C, C) |
(a..b] |
openClosed(C, C) |
(a..+∞) |
greaterThan(C) |
[a..+∞) |
atLeast(C) |
(-∞..b) |
lessThan(C) |
(-∞..b] |
atMost(C) |
(-∞..+∞) |
all() |
此外,也可以明確地指定邊界類型來構造區間:
有界區間 |
range(C, BoundType, C, BoundType) |
無上界區間:((a..+∞) 或[a..+∞)) |
downTo(C, BoundType) |
無下界區間:((-∞..b) 或(-∞..b]) |
upTo(C, BoundType) |
這裏的BoundType是一個枚舉類型,包含CLOSED和OPEN兩個值。
3) 區間運算
Range的基本運算是它的contains(C) 方法,和你期望的一樣,它用來區間判斷是否包含某個值。此外,Range實例也可以當作Predicate,並且在函數式編程中使用(譯者注:見第4章)。任何Range實例也都支持containsAll(Iterable<? extends C>)方法:
4) 查詢運算
Range類提供了以下方法來 查看區間的端點:
hasLowerBound()和hasUpperBound():判斷區間是否有特定邊界,或是無限的;
lowerBoundType()和upperBoundType():返回區間邊界類型,CLOSED或OPEN;如果區間沒有對應的邊界,拋出IllegalStateException;
lowerEndpoint()和upperEndpoint():返回區間的端點值;如果區間沒有對應的邊界,拋出IllegalStateException;
isEmpty():判斷是否爲空區間。
5) 關係運算
a) 包含[enclose]
區間之間的最基本關係就是包含[encloses(Range)]:如果內區間的邊界沒有超出外區間的邊界,則外區間包含內區間。包含判斷的結果完全取決於區間端點的比較
b) 相連[isConnected]
Range.isConnected(Range)判斷區間是否是相連的。具體來說,isConnected測試是否有區間同時包含於這兩個區間,這等同於數學上的定義”兩個區間的並集是連續集合的形式”(空區間的特殊情況除外)。
c) 交集[intersection]
Range.intersection(Range)返回兩個區間的交集:既包含於第一個區間,又包含於另一個區間的最大區間。當且僅當兩個區間是相連的,它們纔有交集。如果兩個區間沒有交集,該方法將拋出IllegalArgumentException。
d) 跨區間[span]
Range.span(Range)返回”同時包括兩個區間的最小區間”,如果兩個區間相連,那就是它們的並集。
span是可互換的[commutative] 、關聯的[associative] 、閉合的[closed]運算[operation]。
6) 離散域
部分(但不是全部)可比較類型是離散的,即區間的上下邊界都是可枚舉的。
在Guava中,用DiscreteDomain<C>實現類型C的離散形式操作。一個離散域總是代表某種類型值的全集;它不能代表類似”素數”、”長度爲5的字符串”或”午夜的時間戳”這樣的局部域。
DiscreteDomain提供的離散域實例包括:
類型 |
離散域 |
Integer |
integers() |
Long |
longs() |
一旦獲取了DiscreteDomain實例,你就可以使用下面的Range運算方法:
ContiguousSet.create(range, domain):用ImmutableSortedSet<C>形式表示Range<C>中符合離散域定義的元素,並增加一些額外操作——譯者注:實際返回ImmutableSortedSet的子類ContiguousSet。(對無限區間不起作用,除非類型C本身是有限的,比如int就是可枚舉的)
canonical(domain):把離散域轉爲區間的”規範形式”。如果ContiguousSet.create(a, domain).equals(ContiguousSet.create(b,domain))並且!a.isEmpty(),則有a.canonical(domain).equals(b.canonical(domain))。(這並不意味着a.equals(b))
你可以創建自己的離散域,但必須記住DiscreteDomain契約的幾個重要方面。
一個離散域總是代表某種類型值的全集;它不能代表類似”素數”或”長度爲5的字符串”這樣的局部域。所以舉例來說,你無法構造一個DiscreteDomain以表示精確到秒的JODA DateTime日期集合:因爲那將無法包含JODA DateTime的所有值。
DiscreteDomain可能是無限的——比如BigInteger DiscreteDomain。這種情況下,你應當用minValue()和maxValue()的默認實現,它們會拋出NoSuchElementException。但Guava禁止把無限區間傳入ContiguousSet.create——譯者注:那明顯得不到一個可枚舉的集合。
9. I/O
1) Guava Files中的文件操作
Java的基本API對文件的操作很繁瑣,爲了向文件中寫入一行文本,都需要寫十幾行的代碼。guava對此作了很多改進,提供了很多方便的操作。
10. 散列(Hash)
1) 概述
Java內建的散列碼[hash code]概念被限制爲32位,並且沒有分離散列算法和它們所作用的數據,因此很難用備選算法進行替換。此外,使用Java內建方法實現的散列碼通常是劣質的,部分是因爲它們最終都依賴於JDK類中已有的劣質散列碼。
Object.hashCode往往很快,但是在預防碰撞上卻很弱,也沒有對分散性的預期。這使得它們很適合在散列表中運用,因爲額外碰撞只會帶來輕微的性能損失,同時差勁的分散性也可以容易地通過再散列來糾正(Java中所有合理的散列表都用了再散列方法)。然而,在簡單散列表以外的散列運用中,Object.hashCode幾乎總是達不到要求——因此,有了com.google.common.hash包。
2) 散列包的組成
在這個包的Java doc中,我們可以看到很多不同的類,但是文檔中沒有明顯地表明它們是怎樣一起配合工作的。
a) HashFunction
HashFunction是一個單純的(引用透明的)、無狀態的方法,它把任意的數據塊映射到固定數目的位置,並且保證相同的輸入一定產生相同的輸出,不同的輸入儘可能產生不同的輸出。
b) Hasher
HashFunction的實例可以提供有狀態的Hasher,Hasher提供了流暢的語法把數據添加到散列運算,然後獲取散列值。Hasher可以接受所有原生類型、字節數組、字節數組的片段、字符序列、特定字符集的字符序列等等,或者任何給定了Funnel實現的對象。
Hasher實現了PrimitiveSink接口,這個接口爲接受原生類型流的對象定義了fluent風格的API
c) Funnel
Funnel描述瞭如何把一個具體的對象類型分解爲原生字段值,從而寫入PrimitiveSink。
注:putString(“abc”,Charsets.UTF_8).putString(“def”, Charsets.UTF_8)完全等同於putString(“ab”, Charsets.UTF_8).putString(“cdef”,Charsets.UTF_8),因爲它們提供了相同的字節序列。這可能帶來預料之外的散列衝突。增加某種形式的分隔符有助於消除散列衝突。
d) HashCode
一旦Hasher被賦予了所有輸入,就可以通過hash()方法獲取HashCode實例(多次調用hash()方法的結果是不確定的)。HashCode可以通過asInt()、asLong()、asBytes()方法來做相等性檢測,此外,writeBytesTo(array, offset, maxLength)把散列值的前maxLength字節寫入字節數組。
3) 布魯姆過濾器[BloomFilter]
布魯姆過濾器是哈希運算的一項優雅運用,它可以簡單地基於Object.hashCode()實現。簡而言之,布魯姆過濾器是一種概率數據結構,它允許你檢測某個對象是一定不在過濾器中,還是可能已經添加到過濾器了。
Guava散列包有一個內建的布魯姆過濾器實現,你只要提供Funnel就可以使用它。你可以使用create(Funnel funnel, int expectedInsertions, doublefalsePositiveProbability)方法獲取BloomFilter<T>,缺省誤檢率[falsePositiveProbability]爲3%。BloomFilter<T>提供了booleanmightContain(T) 和void put(T),它們的含義都不言自明瞭。
4) Hashing類
Hashing類提供了若干散列函數,以及運算HashCode對象的工具方法。
已提供的散列函數
md5() |
murmur3_128() |
murmur3_32() |
sha1() |
sha256() |
sha512() |
goodFastHash(int bits) |
|
HashCode運算
方法 |
描述 |
HashCode combineOrdered( Iterable<HashCode>) |
以有序方式聯接散列碼,如果兩個散列集合用該方法聯接出的散列碼相同,那麼散列集合的元素可能是順序相等的 |
HashCode combineUnordered( Iterable<HashCode>) |
以無序方式聯接散列碼,如果兩個散列集合用該方法聯接出的散列碼相同,那麼散列集合的元素可能在某種排序下是相等的 |
int consistentHash( HashCode, int buckets) |
爲給定的”桶”大小返回一致性哈希值。當”桶”增長時,該方法保證最小程度的一致性哈希值變化。詳見一致性哈希。 |
11. 事件總線(EventBus)
傳統上,Java的進程內事件分發都是通過發佈者和訂閱者之間的顯式註冊實現的。設計EventBus就是爲了取代這種顯示註冊方式,使組件間有了更好的解耦。EventBus不是通用型的發佈-訂閱實現,不適用於進程間通信。
12. 數學運算(Math)
13. 反射(Reflection)
1) guava反射TypeToken解決泛型運行時類型擦除的問題
介紹guava中的TypeToken類解決java 運行時泛型類型擦除問題。
TypeToken的方法列表如下:
方法 |
描述 |
getType() |
獲得包裝的java.lang.reflect.Type. |
getRawType() |
返回大家熟知的運行時類 |
getSubtype(Class<?>) |
返回那些有特定原始類的子類型。舉個例子,如果這有一個Iterable並且參數是List.class,那麼返回將是List。 |
getSupertype(Class<?>) |
產生這個類型的超類,這個超類是指定的原始類型。舉個例子,如果這是一個Set並且參數是Iterable.class,結果將會是Iterable。 |
isAssignableFrom(type) |
如果這個類型是 assignable from 指定的類型,並且考慮泛型參數,返回true。List<? extends Number>是assignable from List,但List沒有. |
getTypes() |
返回一個Set,包含了這個所有接口,子類和類是這個類型的類。返回的Set同樣提供了classes()和interfaces()方法允許你只瀏覽超類和接口類。 |
isArray() |
檢查某個類型是不是數組,甚至是<? extends A[]>。 |
getComponentType() |
返回組件類型數組。 |
2) guava反射之Invokable使用
Guava的Invokable是對java.lang.reflect.Method和java.lang.reflect.Constructor的流式包裝。它簡化了常見的反射代碼的使用。
一些使用例子:
a) 方法是否是public的?
JDK:
Modifier.isPublic(method.getModifiers());
Invokable:
invokable.isPublic();
b) 方法是否是package private?
JDK:
!(Modifier.isPrivate(method.getModifiers())||Modifier.isPublic(method.getModifiers()))
Invokable:
invokable.isPackagePrivate()
c) 方法是否能夠被子類重寫?
JDK:
!(Modifier.isFinal(method.getModifiers())
||Modifiers.isPrivate(method.getModifiers())
||Modifiers.isStatic(method.getModifiers())
||Modifiers.isFinal(method.getDeclaringClass().getModifiers()))
Invokable:
invokable.isOverridable()
d) 方法的第一個參數是否被定義了註解@Nullable?
JDK:
for (Annotation annotation : method.getParameterAnnotations[0]) {
if (annotation instanceof Nullable) {
return true;
}
}
return false;
Invokable:
invokable.getParameters().get(0).isAnnotationPresent(Nullable.class)
e) 構造函數和工廠方法如何共享同樣的代碼?
你是否很想重複自己,因爲你的反射代碼需要以相同的方式工作在構造函數和工廠方法中?
Invokable提供了一個抽象的概念。下面的代碼適合任何一種方法或構造函數:
invokable.isPublic();
invokable.getParameters();
invokable.invoke(object,args);
List的List.get(int)返回類型是什麼?
Invokable提供了與衆不同的類型解決方案:
Invokable<List<String>,?> invokable = new TypeToken<List<String>>() {}.method(getMethod);
invokable.getReturnType();// String.class
3) guava反射:Reflection.newProxy方法簡化動態代理
原理上GoogleGuava的動態代理也是使用JDK的動態代理,這是做了封裝,更加簡便。另外一點是能夠很好的檢查需要代理的對象必須擁有接口。使用Class
類的
isInterface()
來做檢查。
14. 註解(Annotations)
com.google.common.annotations包下註解類的含義和用法:
1) Beta
/**
* 表明一個公用API的未來版本是受不兼容變更或刪除限制的
* 擁有這個註釋標誌的API不受任何兼容性保證
*
*/
@Retention(RetentionPolicy.CLASS)
@Target({
ElementType.ANNOTATION_TYPE,
ElementType.CONSTRUCTOR,
ElementType.FIELD,
ElementType.METHOD,
ElementType.TYPE})
@Documented
@GwtCompatible
public@interface Beta {}
2) GwtCompatible
/**
* 表明一個類型可能會與 Google WebToolkit 一起使用.
* 如果一個方法使用這個註釋,說明這個方法的返回值是GWT 兼容的
*
*/
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.TYPE, ElementType.METHOD })
@Documented
@GwtCompatible
public@interface GwtCompatible {
/**
* 說明一個類型或者方法的返回值是否支持GWT 序列化
*
*/
boolean serializable() default false;
/**
* 說明一個類型是否在 GWT 被模擬.
* 被模擬的源(父源)和JVM的實現不一樣
*
*/
boolean emulated() default false;
}
3) GwtIncompatible
/**
* 說明一個方法可能無法與 GWT 一起使用
* 他只能用於被 @GwtCompatible標誌的類的字段,方法和內部類
*
*/
@Retention(RetentionPolicy.CLASS)
@Target({
ElementType.TYPE, ElementType.METHOD,
ElementType.CONSTRUCTOR,ElementType.FIELD })
@Documented
@GwtCompatible
public@interface GwtIncompatible {
/**
* 用於表示不兼容 GWT 的原因
*
*/
String value();
}
4) VisibleForTesting
/**
*註釋程序元素的存在,或比其他必要廣泛可見,僅用於測試代碼。
*/
@GwtCompatible
public@interface VisibleForTesting {
}
15. 網絡編程(Net)
guava中的net包目前提供的功能較少,而且大多類都標註了@Beta的註解,在guava中標記Beta註解表示這個類還不穩定,有可能在以後的版本中變化,或者去掉,所以不建議大量使用,這裏也是隻做簡單的介紹。
先介紹下唯一一個沒有Beta註解的類HttpHeaders,這個類中並沒有實質的方法,只是定義了一些Http頭名稱的常量,通常如果需要我們會自己定義這些常量,如果你引用了guava包,那麼就不再建議我們自己定義這些頭名稱的常量了,直接用它定義的即可。
這裏面應該有幾乎所有的Http頭名稱,例如:X_FORWARDED_FOR,UPGRADE,REFERER等等。用法也沒有必要介紹了,直接引用常量就可以了。
再介紹下一個比較常用的小功能,有時候我們需要在配置文件中配置IP+端口,這時候需要自己寫解析ip,端口的方法,guava爲我們提供瞭解析類,我們看下用法實例:
HostAndPort hostAndPort =HostAndPort.fromString("127.0.0.1:8080");
System.out.println("host== " + hostAndPort.getHostText());
System.out.println("port== " + hostAndPort.getPortOrDefault(80));
HostAndPort類的靜態方法fromString(String)可以解析出字符串中定義的Host和端口信息。
另外guava包中還提供了InetAddresses類,這個類是InetAddress的幫助類,通過這個類可以方便的從字符串中解析出InetAddress類。但是此類也有@Beta的註解,所以要謹慎使用。
參考資料:http://ifeve.com/google-guava/
https://code.google.com/p/guava-libraries/