JAVA程式設計師必學的Google Guava庫(第一篇)
技術標籤:Java
要說Java程式設計師必須要學習的東西的話,Spring當仁不讓,必須是第一個框架,也有另外一個東西,也是新手程式設計師必須要學習的東西,那就是Google的Guava庫,這個庫的作者,和Effective Java的作者是同一人,都來自於Google,學好了這個庫,你的Java的能力,相信會更上一個臺階。Java的優勢是什麼?那就是各種成熟的輪子,Guava就是其中之一,如果想程式碼很少BUG,就開始使用它吧。
目錄
1、基本工具
2、集合
1、基本工具
1.1 避免NULL
NULL的含義是不確定的,應該避免,尤其是在Map和Set中,返回NULL,並不清楚是不存在,還是有值,但是是NULL,最常用的一個技巧就是:
// 好的程式碼
List arr = new ArrayList();
// 不好的程式碼
List arr = null;
Map<Integer,String> map = new HashMap<>();
//處理預設值
map.getOrDefault(123,"預設值");
//沒有123這個KEY的時候,才執行put方法,如果map中已經有值了,就不執行put方法
//並且返回舊值
map.putIfAbsent(123,"123");
這樣可以避免空指標。Guava的建議是: 使用快速失敗操作拒絕null值對開發者更有幫助。
1.2 Guava用 Optional表示可能為null的T型別引用:
Optional<Integer> possible = Optional.of(50);
possible.isPresent(); // true
possible.get(); // returns 50
2、集合類
2.1 不可變集合
這個屬於防禦性程式設計內容,不可變集合的意思就是集合建立了之後,就不能改變了,類似於:
// SIZE 的值是不可以改變的
final int SIZE = 10;
// final修飾傳統的map,起不到效果
final Map<Integer,String> map = new HashMap< >();
// 不可以
map = new HashMap<>();
// 可以
map.put(123,"123")
想要設計不可變集合類的主要原因有:
- 當物件被不可信的庫呼叫時,不可變形式是安全的。
- 執行緒安全。
- 容易測試。
- 安全,不容易出BUG,以防某些錯誤的程式碼修改了集合的內容。
建立的方法(還有1個copyOf方法,這裡不打算引入了):
final ImmutableSet<Integer> set = ImmutableSet.<Integer>builder()
.add(12)
.add(30)
.add(12)
.build();
ImmutableSortedSet<Integer> sortedSet = ImmutableSortedSet.of(1, 2, 3, 4, 5);
//轉List,取第K小個元素
sortedSet.asList().get(k)
2.2 新集合型別
有一些新的型別可以使用,例如java沒有Table型別,下面將一一介紹。
2.2.1 Multiset
這個東西,可以想象成1個ArrayList,沒有順序,但是它是一個Set,不過可以有重複的Key,還記錄了重複的Key的出現的數量。比方說你要統計一篇文章中出現的單詞的次數:
Map<String, Integer> counts = new HashMap<String, Integer>();
for (String word : words) {
Integer count = counts.get(word);
if (count == null) {
counts.put(word, 1);
} else {
counts.put(word, count + 1);
}
}
這種寫法很笨拙,也容易出錯,並且不支援同時收集多種統計資訊,如總詞數。我們可以做的更好。替代方法:
//模擬文章內容
String [] arr = {"123","123","1234","12345"};
Multiset<String> multiset = HashMultiset.create();
for(String s : arr) {
multiset.add(s);
}
//統計123出現了幾次
System.out.println(multiset.count("123"));
//統計set中一共有幾個元素
System.out.println(multiset.size());
Multiset介面的方法:
方法 | 描述 |
---|---|
count(E) | 給定元素在Multiset中的計數 |
elementSet() | Multiset中不重複元素的集合,型別為Set |
entrySet() | 和Map的entrySet類似,返回Set<Multiset.Entry>,其中包含的Entry支援getElement()和getCount()方法 |
add(E, int) | 增加給定元素在Multiset中的計數 |
remove(E, int) | 減少給定元素在Multiset中的計數 |
setCount(E, int) | 設定給定元素在Multiset中的計數,不可以為負數 |
size() | 返回集合元素的總個數(包括重複的元素) |
該介面的實現類:
Map | 對應的****Multiset | 是否支援null元素 |
---|---|---|
HashMap | HashMultiset | 是 |
TreeMap | TreeMultiset | 是(如果comparator支援的話) |
LinkedHashMap | LinkedHashMultiset | 是 |
ConcurrentHashMap | ConcurrentHashMultiset | 否 |
ImmutableMap | ImmutableMultiset | 否 |
2.2.2 Multimap
一般都遇到過以下場景,這種可以使用Multimap替代,需要注意Multimap不是Map。
//傳統方法
Map<K, List<V>>
Map<K, Set<V>>
// 替代方法 這裡用ArrayListMultimap來舉例
ArrayListMultimap<String,String> arrayListMultimap = ArrayListMultimap.create();
arrayListMultimap.put("abc","1");
arrayListMultimap.put("abc","12");
arrayListMultimap.put("abc","123");
arrayListMultimap.put("abc","1234");
System.out.println(arrayListMultimap.get("abc"));
// [1, 12, 123, 1234]
//arrayListMultimap.get("abc") 返回的是1個List<String>的值
Multimap提供了多種形式的實現。在大多數要使用Map<K, Collection>的地方,你都可以使用它們:
實現 | 鍵行為類似 | 值行為類似 |
---|---|---|
ArrayListMultimap | HashMap | ArrayList |
HashMultimap | HashMap | HashSet |
LinkedListMultimap | LinkedHashMap | LinkedList |
LinkedHashMultimap | LinkedHashMap | LinkedHashMap |
TreeMultimap | TreeMap | TreeSet |
ImmutableListMultimap | ImmutableMap | ImmutableList |
ImmutableSetMultimap | ImmutableMap | ImmutableSet |
2.2.3 BiMap
傳統上,實現鍵值對的雙向對映需要維護兩個單獨的map,並保持它們間的同步。但這種方式很容易出錯,而且對於值已經在map中的情況,會變得非常混亂。例如:
Map<String, Integer> nameToId = Maps.newHashMap();
Map<Integer, String> idToName = Maps.newHashMap();
nameToId.put("Bob", 42);
idToName.put(42, "Bob");
//如果"Bob"和42已經在map中了,會發生什麼?
//如果我們忘了同步兩個map,會有詭異的bug發生...
替代方法:
BiMap<String, Integer> nameIdBiMap = HashBiMap.create();
nameIdBiMap.put("a",1);
nameIdBiMap.put("b",2);
nameIdBiMap.put("c",3);
//nameIdBiMap.put("a",4);
System.out.println("a的id:" + nameIdBiMap.get("a"));
System.out.println("id為1:" + nameIdBiMap.inverse().get(1));
如果不是唯一的,會出現什麼情況呢? 例如2個人重名了。如果我們放開了上面的註釋語句,模擬下這種衝突,就會出現ID為1的人被覆蓋掉了,這點在使用的時候,需要特別注意。
BiMap的各種實現
鍵值實現 | 值鍵實現 | 對應的BiMap實現 |
---|---|---|
HashMap | HashMap | HashBiMap |
ImmutableMap | ImmutableMap | ImmutableBiMap |
EnumMap | EnumMap | EnumBiMap |
EnumMap | HashMap | EnumHashBiMap |
2.3.4 Table
Table有點類似於2維陣列,根據行和列,確認某一個值,沒有Table,老方法就是搞個Map<V,Map<K,V>>這樣的結構來實現。替代方法:
Table<Integer, Integer, Integer> table = HashBasedTable.create();
table.put(1, 1 ,3);
table.put(1, 2, 4);
table.put(2, 1, 5);
table.put(2, 2, 6);
System.out.println(table.row(1)); //{1=3, 2=4}
System.out.println(table.column(1)); //{1=3, 2=5}
System.out.println(table.get(1, 1)); //3
是不是感覺很方便?
2.3.5 RangeSet
這個東西很有意思,在某些場景,我感覺可能會有用,RangeSet的意思是一些非連續區間,例如:
{[1,10], [11,15)} {[1,10], [11,20)} 在數學上,它是不連續的,也可能是連續的。這個時候,想要描述這段區間的時候,可以使用RangeSet這個類。
注:RangeSet不支援GWT,也不支援JDK5和更早版本;因為,RangeSet需要充分利用JDK6中NavigableMap的特性。
RangeSet<Integer> rangeSet = TreeRangeSet.create();
rangeSet.add(Range.closed(1, 10)); // {[1,10]}
rangeSet.add(Range.closedOpen(11, 15));//不相連區間:{[1,10], [11,15)}
rangeSet.add(Range.closedOpen(15, 20)); //相連區間; {[1,10], [11,20)}
rangeSet.add(Range.openClosed(0, 0)); //空區間; {[1,10], [11,20)}
rangeSet.remove(Range.open(5, 10)); //分割[1, 10]; {[1,5], [10,10], [11,20)}
System.out.println(rangeSet.contains(1)); //true
System.out.println(rangeSet.contains(6)); //false
1、RangeSet的實現支援非常廣泛的檢視:
- complement():返回RangeSet的補集檢視。complement也是RangeSet型別,包含了不相連的、非空的區間。
- subRangeSet(Range):返回RangeSet與給定Range的交集檢視。這擴充套件了傳統排序集合中的headSet、subSet和tailSet操作。
- asRanges():用Set<Range>表現RangeSet,這樣可以遍歷其中的Range。
- asSet(DiscreteDomain)(僅ImmutableRangeSet支援):用ImmutableSortedSet表現RangeSet,以區間中所有元素的形式而不是區間本身的形式檢視。(這個操作不支援DiscreteDomain 和RangeSet都沒有上邊界,或都沒有下邊界的情況)
2、RangeSet的查詢方法 為了方便操作,RangeSet直接提供了若干查詢方法,其中最突出的有: - contains©:RangeSet最基本的操作,判斷RangeSet中是否有任何區間包含給定元素。
- rangeContaining©:返回包含給定元素的區間;若沒有這樣的區間,則返回null。
- encloses(Range):簡單明瞭,判斷RangeSet中是否有任何區間包括給定區間。
- span():返回包括RangeSet中所有區間的最小區間。
2.3.6 其它
還有一些其它的類,就不多寫了,可以自己去看看API文件
- ClassToInstanceMap
- RangeMap 這個和RangeSet有點像,相當於某個區間對應了某個值,例如:
RangeMap<Integer, String> rangeMap = TreeRangeMap.create();
rangeMap.put(Range.closed(1, 10), "foo"); //{[1,10] => "foo"}
具體用到了的時候,可以再研究一下。
全文完。 JAVA程式設計師必學的Google Guava庫(第二篇)
參考文件:
併發程式設計網 http://ifeve.com/google-guava/
關注我的部落格,獲取更多Java程式設計知識: 雙King的技術部落格