好用的guava工具類

基礎功能

google guava中定義的String操作

在google guava中爲字符串操作提供了很大的便利,有老牌的判斷字符串是否爲空字符串或者爲null,用指定字符填充字符串,以及拆分合並字符串,字符串匹配的判斷等等。

1. 使用com.google.common.base.Strings類的isNullOrEmpty(input)方法判斷字符串是否爲空

1        //Strings.isNullOrEmpty(input) demo
2         String input = "";
3         boolean isNullOrEmpty = Strings.isNullOrEmpty(input);
4         System.out.println("input " + (isNullOrEmpty?"is":"is not") + " null or empty.");

2. 獲得兩個字符串相同的前綴或者後綴

複製代碼

 1        //Strings.commonPrefix(a,b) demo
 2         String a = "com.jd.coo.Hello";
 3         String b = "com.jd.coo.Hi";
 4         String ourCommonPrefix = Strings.commonPrefix(a,b);
 5         System.out.println("a,b common prefix is " + ourCommonPrefix);
 6 
 7         //Strings.commonSuffix(a,b) demo
 8         String c = "com.google.Hello";
 9         String d = "com.jd.Hello";
10         String ourSuffix = Strings.commonSuffix(c,d);
11         System.out.println("c,d common suffix is " + ourSuffix);

複製代碼

3. Strings的padStart和padEnd方法來補全字符串

1         int minLength = 4;
2         String padEndResult = Strings.padEnd("123", minLength, '0');
3         System.out.println("padEndResult is " + padEndResult);
4 
5         String padStartResult = Strings.padStart("1", 2, '0');
6         System.out.println("padStartResult is " + padStartResult);

4. 使用Splitter類來拆分字符串

Splitter類可以方便的根據正則表達式來拆分字符串,可以去掉拆分結果中的空串,可以對拆分後的字串做trim操作,還可以做二次拆分。

我們先看一個基本的拆分例子:

按 Ctrl+C 複製代碼

 

按 Ctrl+C 複製代碼

Splitter的onPattern方法傳入的是一個正則表達式,其後緊跟的trimResults()方法表示要對結果做trim,omitEmptyStrings()表示忽略空字符串,split方法會執行拆分操作。

split返回的結果爲Iterable<String>,我們可以使用for循環語句來逐個打印拆分字符串的結果。

Splitter還有更強大的功能,做二次拆分,這裏二次拆分的意思是拆分兩次,例如我們可以將a=b;c=d這樣的字符串拆分成一個Map<String,String>。

1         String toSplitString = "a=b;c=d,e=f";
2         Map<String,String> kvs = Splitter.onPattern("[,;]{1,}").withKeyValueSeparator('=').split(toSplitString);
3         for (Map.Entry<String,String> entry : kvs.entrySet()) {
4             System.out.println(String.format("%s=%s", entry.getKey(),entry.getValue()));
5         }

二次拆分首先是使用onPattern做第一次的拆分,然後再通過withKeyValueSeperator('')方法做第二次的拆分。

5. 有拆分字符串必然就有合併字符串,guava爲我們提供了Joiner類來做字符串的合併

我們先看一個簡單的示例:

1         String joinResult = Joiner.on(" ").join(new String[]{"hello","world"});
2         System.out.println(joinResult);

面例子中我們使用Joiner.on(" ").join(xx)來合併字符串。很簡單也很有效。

Splitter方法可以對字符串做二次的拆分,對應的Joiner也可以逆向操作,將Map<String,String>做合併。我們看下下面的例子:

1         Map<String,String> map = new HashMap<String,String>();
2         map.put("a", "b");
3         map.put("c", "d");
4         String mapJoinResult = Joiner.on(",").withKeyValueSeparator("=").join(map);
5         System.out.println(mapJoinResult);

使用withKeyValueSeparator方法可以對map做合併。合併的結果是:a=b,c=d

guava庫中還可以對字符串做大小寫轉換(CaseFormat枚舉),可以對字符串做模式匹配。使用起來都很方便。

 

guava中的對象操作封裝

在開發中經常會需要比較兩個對象是否相等,這時候我們需要考慮比較的兩個對象是否爲null,然後再調用equals方法來比較是否相等,google guava庫的com.google.common.base.Objects類提供了一個靜態方法equals可以避免我們自己做是否爲空的判斷,示例如下:

1  Object a = null;
2  Object b = new Object();
3 boolean aEqualsB = Objects.equal(a, b);

Objects.equals的實現是很完美的,其實現代碼如下:

1 public static boolean equal(@Nullable Object a, @Nullable Object b) {
2     return a == b || (a != null && a.equals(b));
3   }

首先判斷a b是否是同一個對象,如果是同一對象,那麼直接返回相等,如果不是同一對象再判斷a不爲null並且a.equals(b). 這樣做既考慮了性能也考慮了null空指針的問題。

另外Objects類中還爲我們提供了方便的重寫toString()方法的機制,我們通過例子來了解一下吧:

複製代碼

 1 import com.google.common.base.Objects;
 2 
 3 public class ObjectsDemo {
 4     public static void main(String [] args) {
 5       Student jim = new Student();
 6         jim.setId(1);
 7         jim.setName("Jim");
 8         jim.setAge(13);
 9         System.out.println(jim.toString());
10     }
11 
12     public static class Student {
13         private int id;
14         private String name;
15         private int age;
16 
17         public int getId() {
18             return id;
19         }
20         public void setId(int id) {
21             this.id = id;
22         }
23 
24         public String getName() {
25             return name;
26         }
27         public void setName(String name) {
28             this.name = name;
29         }
30 
31         public int getAge() {
32             return age;
33         }
34         public void setAge(int age) {
35             this.age = age;
36         }
37 
38         public String toString() {
39             return Objects.toStringHelper(this.getClass())
40                     .add("id", id)
41                     .add("name", name)
42                     .add("age", age)
43                     .omitNullValues().toString();
44         }
45     }
46 }

複製代碼

我們定義了一個Student類,該類有三個屬性,分別爲id,name,age,我們重寫了toString()方法,在這個方法中我們使用了Objects.toStringHelper方法,首先指定toString的類,然後依次add屬性名稱和屬性值,可以使用omitNullValues()方法來指定忽略空值,最後調用其toString()方法,就可以得到一個格式很好的toString實現了。

上面代碼輸出的結果是:

Student{id=1, name=Jim, age=13}

這種方式寫起來很簡單,可讀性也很好,所以用Guava吧。

guava的Preconditions使用

guava的base包中提供的Preconditions類用來方便的做參數的校驗,他主要提供如下方法:

  1. checkArgument 接受一個boolean類型的參數和一個可選的errorMsg參數,這個方法用來判斷參數是否符合某種條件,符合什麼條件google guava不關心,在不符合條件時會拋出IllegalArgumentException異常
  2. checkState 和checkArgument參數和實現基本相同,從字面意思上我們也可以知道這個方法是用來判斷狀態是否正確的,如果狀態不正確會拋出IllegalStateException異常
  3. checkNotNull方法用來判斷參數是否不是null,如果爲null則會拋出NullPointerException空指針異常
  4. checkElementIndex方法用來判斷用戶傳入的數組下標或者list索引位置,是否是合法的,如果不合法會拋出IndexOutOfBoundsException
  5. checkPositionIndexes方法的作用和checkElementIndex方法相似,只是此方法的索引範圍是從0到size包括size,而上面的方法不包括size。

下面我們看一個具體的使用示例:

複製代碼

 1 import com.google.common.base.Preconditions;
 2 
 3 public class PreconditionsDemo {
 4     public static void main(String[] args) {
 5         PreconditionsDemo demo = new PreconditionsDemo();
 6         demo.doSomething("Jim", 19, "hello world, hello java");
 7     }
 8 
 9     public void doSomething(String name, int age, String desc) {
10         Preconditions.checkNotNull(name, "name may not be null");
11         Preconditions.checkArgument(age >= 18 && age < 99, "age must in range (18,99)");
12         Preconditions.checkArgument(desc !=null && desc.length() < 10, "desc too long, max length is ", 10);
13 
14         //do things
15     }
16 }

複製代碼

上面例子中的doSomething()方法調用了三次Preconditions的方法,來對參數做校驗。

看似Preconditions實現很簡單,他的意義在於爲我們提供了同一的參數校驗,並對不同的異常情況拋出合適類型的異常,並對異常信息做格式化。

 

使用google guava的Optional接口來避免空指針錯誤

null會帶來很多問題,從開始有null開始有無數程序栽在null的手裏,null的含義是不清晰的,檢查null在大多數情況下是不得不做的,而我們又在很多時候忘記了對null做檢查,在我們的產品真正投入使用的時候,空指針異常出現了,這是一種討厭的情況。

鑑於此google的guava庫中提供了Optional接口來使null快速失敗,即在可能爲null的對象上做了一層封裝,在使用Optional靜態方法of時,如果傳入的參數爲null就拋出NullPointerException異常。

我們看一個實際的例子:

複製代碼

1 import com.google.common.base.Optional;
2 
3 public class OptionalDemo {
4     public static void main(String[] args) {
5         Optional<Student> possibleNull = Optional.of(null);
6         possibleNull.get();
7     }
8     public static class Student { }
9 }

複製代碼

上面的程序,我們使用Optional.of(null)方法,這時候程序會第一時間拋出空指針異常,這可以幫助我們儘早發現問題。

我們再看另外一個例子,我們使用Optional.absent方法來初始化posibleNull實例,然後我們get此對象,看看會是什麼情況。

複製代碼

1 public class OptionalDemo {
2     public static void main(String[] args) {
3         Optional<Student> possibleNull = Optional.absent();
4         Student jim = possibleNull.get();
5     }
6     public static class Student { }
7 }

複製代碼

運行上面的程序,發現出現了:Exception in thread "main" java.lang.IllegalStateException: Optional.get() cannot be called on an absent value。

這樣使用也會有異常出來,那Optional到底有什麼意義呢?

使用Optional除了賦予null語義,增加了可讀性,最大的優點在於它是一種傻瓜式的防護。Optional迫使你積極思考引用缺失的情況,因爲你必須顯式地從Optional獲取引用。直接使用null很容易讓人忘掉某些情形,儘管FindBugs可以幫助查找null相關的問題,但是我們還是認爲它並不能準確地定位問題根源。

如同輸入參數,方法的返回值也可能是null。和其他人一樣,你絕對很可能會忘記別人寫的方法method(a,b)會返回一個null,就好像當你實現method(a,b)時,也很可能忘記輸入參數a可以爲null。將方法的返回類型指定爲Optional,也可以迫使調用者思考返回的引用缺失的情形。

google guava Throwables幫你拋出異常,處理異常

guava類庫中的Throwables提供了一些異常處理的靜態方法,這些方法的從功能上分爲兩類,一類是幫你拋出異常,另外一類是幫你處理異常。

也許你會想:爲什麼要幫我們處理異常呢?我們自己不會拋出異常嗎?

假定下面的方法是我們要調用的方法。

複製代碼

1  public void doSomething() throws Throwable {
2         //ignore method body
3     }
4 
5     public void doSomethingElse() throws Exception {
6         //ignore method body
7     }

複製代碼

這兩個方法的簽名一個throws出了Throwable另外一個throws出了Exception,他們沒有定義具體會拋出什麼異常,也就是說他們什麼異常都有可能拋出來,如果我們要調用這樣的方法,就需要對他們的異常做一些處理了,我們需要判斷什麼樣的異常需要拋出去,什麼樣的異常需要封裝成RuntimeException。而這些事情就是Throwables類要幫我們做的事情。

假定我們要實現一個doIt的方法,該方法要調用doSomething方法,而doIt的定義中只允許拋出SQLException,我們可以這樣做:

複製代碼

1     public void doIt() throws SQLException {
2         try {
3             doSomething();
4         } catch (Throwable throwable) {
5             Throwables.propagateIfInstanceOf(throwable, SQLException.class);
6             Throwables.propagateIfPossible(throwable);
7         }
8     }

複製代碼

請注意doIt的catch塊,下面這行代碼的意思是如果異常的類型是SQLException,那麼拋出這個異常

Throwables.propagateIfInstanceOf(throwable, SQLException.class);

第二行表示如果異常是Error類型,那麼拋出這個類型,否則將拋出RuntimeException,我們知道RuntimeException是不需要在throws中聲明的。

Throwables.propagateIfPossible(throwable);

Throwables類還爲我們提供了一些方便的異常處理幫助方法:

  1. 我們可以通過Throwables.getRooCause(Throwable)獲得根異常
  2. 可以使用getCausalChain方法獲得異常的列表
  3. 可以通過getStackTraceAsString獲得異常堆棧的字符串

集合增強

guava的不可變集合

 

不可變集合的意義

不可變對象有很多優點,包括:

  • 當對象被不可信的庫調用時,不可變形式是安全的;
  • 不可變對象被多個線程調用時,不存在競態條件問題
  • 不可變集合不需要考慮變化,因此可以節省時間和空間。所有不可變的集合都比它們的可變形式有更好的內存利用率(分析和測試細節);
  • 不可變對象因爲有固定不變,可以作爲常量來安全使用。

創建對象的不可變拷貝是一項很好的防禦性編程技巧。Guava爲所有JDK標準集合類型和Guava新集合類型都提供了簡單易用的不可變版本。
 JDK也提供了Collections.unmodifiableXXX方法把集合包裝爲不可變形式,但我們認爲不夠好:

  • 笨重而且累贅:不能舒適地用在所有想做防禦性拷貝的場景;
  • 不安全:要保證沒人通過原集合的引用進行修改,返回的集合纔是事實上不可變的;
  • 低效:包裝過的集合仍然保有可變集合的開銷,比如併發修改的檢查、散列表的額外空間,等等。

如果你沒有修改某個集合的需求,或者希望某個集合保持不變時,把它防禦性地拷貝到不可變集合是個很好的實踐。

重要提示:所有Guava不可變集合的實現都不接受null值。我們對Google內部的代碼庫做過詳細研究,發現只有5%的情況需要在集合中允許null元素,剩下的95%場景都是遇到null值就快速失敗。如果你需要在不可變集合中使用null,請使用JDK中的Collections.unmodifiableXXX方法。更多細節建議請參考“使用和避免null”

如何使用guava的不可變集合

1. 如何創建不可變集合

第一種方法使用builder創建:

複製代碼

 1 public class ImmutableDemo {
 2     public static void main(String[] args) {
 3         Set<String> immutableNamedColors = ImmutableSet.<String>builder()
 4                 .add("red", "green","black","white","grey")
 5                 .build();
 6         //immutableNamedColors.add("abc");
 7         for (String color : immutableNamedColors) {
 8             System.out.println(color);
 9         }
10     }
11 }

複製代碼

第二種方法使用of靜態方法創建:

        ImmutableSet.of("red","green","black","white","grey");

第三種方法使用copyOf靜態方法創建:

        ImmutableSet.copyOf(new String[]{"red","green","black","white","grey"});

2. 使用asList()獲得不可變集合的list視圖

asList方法是在ImmutableCollection中定義,而所有的不可變集合都會從ImmutableCollection繼承,所以所有的不可變集合都會有asList()方法返回當前不可變集合的list視圖,這個視圖也是不可變的。

3. 不可變集合的使用

不可變集合的使用和普通集合一樣,只是不能使用他們的add,remove等修改集合的方法。

 

guava集合之Multiset

Multiset看似是一個Set,但是實質上它不是一個Set,它沒有繼承Set接口,它繼承的是Collection<E>接口,你可以向Multiset中添加重複的元素,Multiset會對添加的元素做一個計數。

它本質上是一個Set加一個元素計數器。

複製代碼

 1 import com.google.common.base.Splitter;
 2 import com.google.common.collect.HashMultiset;
 3 import com.google.common.collect.Multiset;
 4 
 5 public class MultisetDemo {
 6     public static void main(String[] args) {
 7         Multiset multiset = HashMultiset.create();
 8         String sentences = "this is a story, there is a good girl in the story.";
 9         Iterable<String> words = Splitter.onPattern("[^a-z]{1,}").omitEmptyStrings().trimResults().split(sentences);
10         for (String word : words) {
11             multiset.add(word);
12         }
13 
14         for (Object element : multiset.elementSet()) {
15             System.out.println((String)element + ":" + multiset.count(element));
16         }
17     }
18 }

複製代碼

在上面的示例中我們對一段文字拆分成一個一個的單詞,然後依次放入到multiset中,注意這段文字中有多個重複的單詞,然後我們通過for循環遍歷multiset中的每一個元素,並輸出他們的計數。輸出內容如下:

story:2
is:2
girl:1
there:1
a:2
good:1
the:1
in:1
this:1

顯然計數不是問題,Multiset還提供了add和remove的重載方法,可以在add或這remove的同時指定計數的值。

常用實現 Multiset 接口的類有:

  • HashMultiset: 元素存放於 HashMap
  • LinkedHashMultiset: 元素存放於 LinkedHashMap,即元素的排列順序由第一次放入的順序決定
  • TreeMultiset:元素被排序存放於TreeMap
  • EnumMultiset: 元素必須是 enum 類型
  • ImmutableMultiset: 不可修改的 Mutiset

看到這裏你可能已經發現 Guava Collections 都是以 create 或是 of 這樣的靜態方法來構造對象。這是因爲這些集合類大多有多個參數的私有構造方法,由於參數數目很多,客戶代碼程序員使用起來就很不方便。而且以這種方式可以返回原類型的子類型對象。另外,對於創建範型對象來講,這種方式更加簡潔。

 

google guava的BiMap:雙向Map

我們知道Map是一種鍵值對映射,這個映射是鍵到值的映射,而BiMap首先也是一種Map,他的特別之處在於,既提供鍵到值的映射,也提供值到鍵的映射,所以它是雙向Map.

想象這麼一個場景,我們需要做一個星期幾的中英文表示的相互映射,例如Monday對應的中文表示是星期一,同樣星期一對應的英文表示是Monday。這是一個絕好的使用BiMap的場景。

複製代碼

 1 mport com.google.common.collect.BiMap;
 2 import com.google.common.collect.HashBiMap;
 3 
 4 public class BiMapDemo {
 5     public static void main(String[] args) {
 6         BiMap<String,String> weekNameMap = HashBiMap.create();
 7         weekNameMap.put("星期一","Monday");
 8         weekNameMap.put("星期二","Tuesday");
 9         weekNameMap.put("星期三","Wednesday");
10         weekNameMap.put("星期四","Thursday");
11         weekNameMap.put("星期五","Friday");
12         weekNameMap.put("星期六","Saturday");
13         weekNameMap.put("星期日","Sunday");
14 
15         System.out.println("星期日的英文名是" + weekNameMap.get("星期日"));
16         System.out.println("Sunday的中文是" + weekNameMap.inverse().get("Sunday"));
17     }
18 }

複製代碼

BiMap的值鍵對的Map可以通過inverse()方法得到。

BiMap的常用實現有:

  1. HashBiMap: key 集合與 value 集合都有 HashMap 實現
  2. EnumBiMap: key 與 value 都必須是 enum 類型
  3. ImmutableBiMap: 不可修改的 BiMap

google guava的Multimaps:一鍵多值的Map

有時候我們需要這樣的數據類型Map<String,Collection<String>>,guava中的Multimap就是爲了解決這類問題的。

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

我們通過一個示例來了解Multimap的使用方法:

複製代碼

 1 public class MutliMapTest {  
 2     public static void main(String... args) {  
 3   Multimap<String, String> myMultimap = ArrayListMultimap.create();  
 4     
 5   // 添加鍵值對
 6   myMultimap.put("Fruits", "Bannana"); 
 7  //給Fruits元素添加另一個元素 
 8   myMultimap.put("Fruits", "Apple");  
 9   myMultimap.put("Fruits", "Pear");  
10   myMultimap.put("Vegetables", "Carrot");  
11     
12   // 獲得multimap的size
13   int size = myMultimap.size();  
14   System.out.println(size);  // 4  
15     
16   // 獲得Fruits對應的所有的值
17   Collection<string> fruits = myMultimap.get("Fruits");  
18   System.out.println(fruits); // [Bannana, Apple, Pear]  
19     
20   Collection<string> vegetables = myMultimap.get("Vegetables");  
21   System.out.println(vegetables); // [Carrot]  
22     
23   //遍歷Mutlimap  
24   for(String value : myMultimap.values()) {  
25    System.out.println(value);  
26   }  
27     
28   // Removing a single value  
29   myMultimap.remove("Fruits","Pear");  
30   System.out.println(myMultimap.get("Fruits")); // [Bannana, Pear]  
31     
32   // Remove all values for a key  
33   myMultimap.removeAll("Fruits");  
34   System.out.println(myMultimap.get("Fruits")); // [] (Empty Collection!)  
35 }  
36 }  

複製代碼

google guava集合之Table

在guava庫中還提供了一種二維表結構:Table。使用Table可以實現二維矩陣的數據結構,可以是稀溜矩陣。

我們看一個使用示例:

複製代碼

 1 public class TableDemo {
 2     public static void main(String[] args) {
 3         Table<Integer, Integer, String> table = HashBasedTable.create();
 4         for (int row = 0; row < 10; row++) {
 5             for (int column = 0; column < 5; column++) {
 6                 table.put(row, column, "value of cell (" + row + "," + column + ")");
 7             }
 8         }
 9         for (int row=0;row<table.rowMap().size();row++) {
10             Map<Integer,String> rowData = table.row(row);
11             for (int column =0;column < rowData.size(); column ++) {
12                 System.out.println("cell(" + row + "," + column + ") value is:" + rowData.get(column));
13             }
14         }
15     }
16 }

複製代碼

在上面示例中我們通過HashBasedTable創建了一個行類型爲Integer,列類型也爲Integer,值爲String的Table。然後我們使用put方法向Table中添加了一些值,然後顯示這些值

Guava集合:使用Iterators簡化Iterator操作

Iterators是Guava中對Iterator迭代器操作的幫助類,這個類提供了很多有用的方法來簡化Iterator的操作。

1. 判斷迭代器中的元素是否都滿足某個條件 all 方法

複製代碼

 1         List<String> list = Lists.newArrayList("Apple","Pear","Peach","Banana");
 2 
 3         Predicate<String> condition = new Predicate<String>() {
 4             @Override
 5             public boolean apply(String input) {
 6                 return ((String)input).startsWith("P");
 7             }
 8         };
 9         boolean allIsStartsWithP = Iterators.all(list.iterator(), condition);
10         System.out.println("all result == " + allIsStartsWithP);

複製代碼

all方法的第一個參數是Iterator,第二個參數是Predicate<String>的實現,這個方法的意義是不需要我們自己去寫while循環了,他的內部實現中幫我們做了循環,把循環體中的條件判斷抽象出來了。

2. 通過any判斷迭代器中是否有一個滿足條件的記錄,any方法的參數和all方法一樣,就不再具體舉例了

3. get方法獲得迭代器中的第x個元素

 String secondElement = Iterators.get(list.iterator(), 1);

4. filter方法過濾符合條件的項

 

複製代碼

1        Iterator<String> startPElements = Iterators.filter(list.iterator(), new Predicate<String>() {
2             @Override
3             public boolean apply(String input) {
4                 return input.startsWith("P");
5             }
6         });

複製代碼

filter方法的第一個參數是源迭代器,第二個參數是Predicate的實現,其apply方法會返回當前元素是否符合條件。

 

5. find方法返回符合條件的第一個元素

1         String length5Element = Iterators.find(list.iterator(), new Predicate<String>() {
2             @Override
3             public boolean apply(String input) {
4                 return input.length() == 5;
5             }
6         });

6. transform方法,對迭代器元素做轉換

複製代碼

1         Iterator<Integer> countIterator = Iterators.transform(list.iterator(), new Function<String, Integer>() {
2             @Override
3             public Integer apply(String input) {
4                 return input.length();
5             }
6         });

複製代碼

上面的例子中我們將字符串轉換成了其長度,transform方法輸出的是另外一個Iterator.

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章