基本Guava工具

 
使用Joiner類
將任意字符串經過分隔符進行鏈接到一塊兒是大多程序員常常作的事情。他們常用array,list,iterable而且循環變量將每個臨時變量添加到StringBuilder當中去,而且中間添加分隔符。這些笨重的處理方式以下:
public String buildString(List<String> stringList, String delimiter){
  StringBuilder builder = new StringBuilder();
  for (String s : stringList) {
    if(s !=null){
      builder.append(s).append(delimiter);
    }
  }
  builder.setLength(builder.length() – delimiter.length());
  return builder.toString();
}
注意要刪除在最後面的分隔符。不是很難懂,可是使用Joiner類能夠獲得簡單的代碼模板。一樣的例子使用Joiner類代碼以下:
public String buildString(List<String> stringList, String delimiter){ 
       return  Joiner.on(delimiter).skipNulls().join(stringList);
}
這樣更加簡明而且不會出錯。若是你想將null值替換掉,可使用以下方法:
Joiner.on("|").useForNull("no value").join(stringList);
使用Joiner類有幾點須要注意。Joiner類不只僅能夠處理字符串的array、list、iterable,他還能夠處理任何對象的array、list、iterable。結果就是調用每個元素的toString()方法。所以,若是沒有使用skipNulls或者useForNull,就會拋出空指針異常。Joiner對象一旦被建立就是不可變的,因此他們是線程安全的,能夠被看成常亮來看待。而後看看下面的代碼片斷:
  
Joiner stringJoiner = Joiner.on("|").skipNulls();
  //使用useForNull方法將會返回一個新的Joiner實例
  stringJoiner.useForNull("missing");
  stringJoiner.join("foo","bar",null);
在上面的代碼實例當中,useForNull方法並無起做用,null值仍然被忽略了。
Joiner不只僅能返回字符串,還能夠與StringBuilder一塊兒使用:
StringBuilder stringBuilder = new StringBuilder();
Joiner joiner = Joiner.on("|").skipNulls();
//返回的StringBuilder實例當中包含鏈接完成的字符串
joiner.appendTo(stringBuilder,"foo","bar","baz");
上面的例子,咱們傳入一個StringBuilder的參數而且返回一個StringBuilder實例。
Joiner類也可使用實現了Appendable接口的類。
FileWriter fileWriter = new FileWriter(new File("path")):
List<Date> dateList = getDates();
Joiner joiner = Joiner.on("#").useForNulls(" ");
// 返回由字符串拼接後的FileWriter實例 
joiner.appendTo(fileWriter,dateList);
這是一個與上一個類似的例子。咱們傳入一個FileWriter實例和一組數據,就會將這組數據拼接後附加到FileWriter當中而且返回。
咱們能夠看到,Joiner使一些公共的操做變的很是簡單。有一個特殊的方法會實現MapJoiner方法,MapJoiner方法像Joiner同樣使用分割符將每組key與value分開,同時key與value之間也有個分隔符。MapJoiner方法的建立以下:
MapJoiner mapJoiner = Joiner.on("#").withKeyValueSeparator("=");
快速回顧一下上面內容:
  • Joiner.on("#")方法會建立一個Joiner的實例。
  • 使用返回的Joiner實例調用withKeyValueSeparator方法將會返回MapJoiner對象。
下面是對MapJoiner方法的單元測試代碼:
@Test
public void testMapJoiner() {
  String expectedString = "Washington D.C=Redskins#New York City=Giants#Philadelphia=Eagles#Dallas=Cowboys"; 
  Map<String,String> testMap = Maps.newLinkedHashMap();
  testMap.put("Washington D.C","Redskins");
  testMap.put("New York City","Giants");
  testMap.put("Philadelphia","Eagles");
  testMap.put("Dallas","Cowboys");
  String returnedString = Joiner.on("#"). withKeyValueSeparator("=").join(testMap); 
  assertThat(returnedString,is(expectedString));
}
回顧時間
上面的單元測試開始時建立一個key和value都是字符串的LinkedHashMap實例,值得注意的是咱們使用靜態工廠方法newLinkedHashMap()來建立,Maps類是在com.google.common. collect包當中。而後使用Joiner類建立一個使用key與value拼接的字符串。最後咱們斷言他是否與咱們指望返回的字符串相同。
 
使用Splitter類
程序員另外一個常常處理的問題是對字符串以特定分隔符進行分割而且獲取一個字符串數組。若是你須要讀取文本文件,你老是會作這樣的事情。可是
String.split方法不夠完美,看下面的例子:
String testString = "Monday,Tuesday,,Thursday,Friday,,";
//parts is [Monday, Tuesday, , Thursday,Friday]
String[] parts = testString.split(",");
能夠看到,String.split方法省略了最後的2個空串。在有些時候,這個作法是你須要的,可是這些事情是應該由程序員來決定是否省略。Splitter類能夠幫助咱們實現與Joiner類相反的功能。Splitter可使用單個字符、固定字符串、正則表達式串、正則表達式對象或者CharMatcher對象(另外一個Guava的類,本章會講到)來分割字符串。能夠給定具體分割符來建立Splitter對象而後使用。一旦擁有了Splitter實例後就能夠調用split方法,而且會返回包含分割後字符串的迭代器對象。
Splitter.on('|').split("foo|bar|baz");
Splitter splitter = Splitter.on("\\d+");
在上面的例子當中,咱們看到一個Splitter 實例使用了'|'字符分割,另一個實例使用了正則表達式進行分割。
Splitter有一個能夠處理前面空格和後面空格的方法是trimResults。
//Splits on '|' and removes any leading or trailing whitespace
Splitter splitter = Splitter.on('|').trimResults();
與Joiner類同樣Splitter類一樣是一個不可變的類,因此在使用的時候應該使用調用trimResults方法後返回的Splitter實例。
Splitter splitter = Splitter.on('|');
//Next call returns a new instance, does not
modify the original!
splitter.trimResults();
//Result would still contain empty elements
Iterable<String> parts = splitter.split("1|2|3|||");
Splitter 類,像Joiner與MapJoiner同樣也有MapSplitter類。MapSplitter類能夠將字符串轉換成Map實例返回,而且元素的順序與字符串給定的順序相同。使用下面方法構造一個MapSplitter實例:
//MapSplitter is defined as an inner class of Splitter
Splitter.MapSplitter mapSplitter = Splitter.on("#"). withKeyValueSeparator("=");
能夠看到MapSplitter的建立方式與MapJoiner同樣。首先給Splitter指定一個分隔符,而後指定MapSplitter對象key與value的分隔符。下面是一個關於MapSplitter的例子,實現的是與MapJoiner相反地功能。
@Test
public void testSplitter() {
  String startString = "Washington D.C=Redskins#New York City=Giants#Philadelphia=Eagles#Dallas=Cowboys"; 
  Map<String,String> testMap = Maps.newLinkedHashMap();
  testMap.put("Washington D.C","Redskins");
  testMap.put("New York City","Giants");
  testMap.put("Philadelphia","Eagles");
  testMap.put("Dallas","Cowboys");
  Splitter.MapSplitter mapSplitter = Splitter.on("#").withKeyValueSeparator("="); 
  Map<String,String> splitMap = mapSplitter.split(startSring); 
  assertThat(testMap,is(splitMap));
}
 
回顧時間
上面的單元測試是將一個字符串用MapSplitter分割成一個LinkedHashMap的實例。而後斷言這個返回的LinkedHashMap實例是否與咱們指望是LinkedHashMap實例相同。
Joiners 和Splitters 這兩個類應該是每一個java開發人員的工具。
 
使用Guava操做字符串
不管你喜歡的哪一種語言,全部跟字符串相關的事情都是無聊乏味的而且容易出錯。有時候,咱們須要從文件或者數據當中讀取數據而且從新格式化這些數據提交給用戶,或者以固定的格式來保存這些數據。幸運的是,Guava提供給咱們很是好用的類使咱們操做字符串更加輕鬆。這些類以下:
  • CharMatcher
  • Charsets
  • Strings
如今咱們看一看如何在代碼中使用它們。
在第一個例子當中,這個單元測試將會展現使用ASCII類方法來肯定一個字符是不是小寫。第二個例子將展現將小寫字符串轉換成大寫。
 
使用Charsets類
在java當中,有6個標準字符集在每個java平臺都會被支持。這與常常運行下面代碼是相關的:
byte[] bytes = someString.getBytes();
可是有一個問題關於上面的代碼。沒有指定你想返回字節的字符集,你將會得到系統使用運行時默認的字符集返回的字節,這可能會產生問題有能夠默認字符集不是你想要的。因此最佳的作法是像下面這樣:
try{
  bytes = "foobarbaz".getBytes("UTF-8");
}catch (UnsupportedEncodingException e){
  //This really can't happen UTF-8 must be supported
}
可是仍然有兩個問題在上面的代碼當中:
  • UTF-8在java平臺 必定會被支持,因此UnsupportedEncodingException必定不會被拋出
  • 一旦咱們使用字符串指定字符集的定義,咱們能夠產生拼寫錯誤而後致使異常拋出。
Charsets類能夠幫助咱們,Charsets類提供了static final 的六個java平臺支持的字符集。使用Charsets類咱們可使上面的例子更簡單些:
byte[] bytes2 = "foobarbaz".getBytes(Charsets.UTF_8);
值得注意的是在JAVA7當中,StandardCharsets也提供了一樣的功能。如今咱們看看Strings類。
 
使用Strings類
Strings類提供一些便利實用的方法處理字符串。你是否寫過像下面的代碼:
StringBuilder builder = new StringBuilder("foo");
  char c = 'x';
  for(int i=0; i<3; i++){
    builder.append(c);
  }
return builder.toString();
上面例子當中的6行代碼咱們可使用一行代碼來替換。
Strings.padEnd("foo",6,'x');
第二個參數是很重要的,6指定返回的字符串最小長度爲6,而不是指定'x'字符在字符串後面追加多少次。若是提供的字符串長度已經大於了6,則不會進行追加。一樣也有一個相相似的padStart方法能夠在給定字符串的前面追加字符到指定的長度。
在Strings類當中有三個很是有用的方法來處理空值的:
  • nullToEmpty:這個方法接受一個字符串參數,若是傳入的參數不是null值或者長度大於0則原樣返回,不然返回空串("");
  • emptyToNull:這個方法相似於nullToEmpty,它將返回null值若是傳入的參數是空串或者null。
  • isNullOrEmpty:這個方法會檢查傳入參數是否爲null和長度,若是是null和長度爲0就返回true。
或許,這將是一個不錯的主意老是在任何使用nullToEmpty方法處理傳入的字符串參數。
 
使用CharMatcher類
CharMatcher提供了在必定字符串中匹配是否存在特定字符串的功能。在CharMatcher類當中的方法也可讓格式化更加簡單。例如,你能夠將多行的字符串格式化成一行,而且換行符將會以空格來代替。
CharMatcher.BREAKING_WHITESPACE.replaceFrom(stringWithLinebreaks,' ');
還有一個版本replaceFrom的,須要一個CharSequence的值做爲第2個參數值,而不是一個單一的字符。
移除多個空格和tab以單個空格來代替,代碼以下:
@Test
public void testRemoveWhiteSpace(){
  String tabsAndSpaces = "String with spaces and         tabs"; 
  String expected = "String with spaces and tabs";
  String scrubbed = CharMatcher.WHITESPACE.collapseFrom(tabsAndSpaces,' ');
  assertThat(scrubbed,is(expected));
}
在上面的測試代碼中,咱們把全部多個空格和tab都替換成了一個空格,全部都在一行上。
上面例子在某些狀況下可行,可是若是字符串在開頭就有空格咱們想移除怎麼辦?返回的字符串當中前面依然會包含空格,這時可使用trimAndCollapseFrom方法:
@Test
public void testTrimRemoveWhiteSpace(){
  String tabsAndSpaces = "   String with spaces and       tabs"; 
  String expected = "String with spaces and tabs";
  String scrubbed = CharMatcher.WHITESPACE. trimAndCollapseFrom(tabsAndSpaces,' '); 
  assertThat(scrubbed,is(expected));
}
在這個測試當中,咱們再一次將把多個空格和tab替換成一個空格也在一行上。
列出全部在CharMatcher類可用的方法是不切實際的,這裏是否是更換一組匹配字符的地方,咱們保留所匹配的字符的例子: 
@Test
    public void retainFromTest()
    {
        String lettersAndNumbers = "foo989yxbar234";
        String expected = "989234";
        String actual = CharMatcher.JAVA_DIGIT.retainFrom(lettersAndNumbers);
        assertEquals(expected, actual);
    }
在這個例子當中咱們找到"foo989yxbar234"字符串當中因此的數字而且保留下來。
往下繼續以前,咱們應該看看最後一個CharMatcher類中的強大功能。能夠聯合多個CharMatcher類實例建立一個新的CharMatcher類實例。
假設你須要創一個匹配數字和空格的CharMatcher類實例:
CharMatcher cm = CharMatcher.JAVA_DIGIT.or(CharMatcher.WHITESPACE);
它如今將會匹配任何數字或者空格。CharMatcher類對於操做字符串來講功能強大而且很好用。
 
使用Preconditions類
Preconditions類是用來驗證咱們的代碼狀態的靜態方法的集合。 Preconditions很是重要,由於他們保證咱們的指望執行成功的代碼獲得知足。 若是條件與咱們指望的不一樣,咱們會及時反饋問題。和之前同樣,使用前提條件的重要性是確保咱們代碼的行爲,並在調試中頗有用。
你能夠寫你本身的先決條件,像下面這樣:
if(someObj == null){
    throw new IllegalArgumentException(" someObj must not be null"); 
}
使用Preconditions當中的方法(須要靜態導入),檢查一個空值更簡單。
checkNotNull(someObj,"someObj must not be null");
接下來,咱們將要展現使用先決條件的例子:
public class PreconditionExample {
  private String label;
  private int[] values = new int[5];
  private int currentIndex;
  public PreconditionExample(String label) {
    //返回label若是不爲空
    this.label = checkNotNull(label,"Label can''t be null");
  }
  public void updateCurrentIndexValue(int index, int valueToSet) {
    //檢查索引是否有效
    this.currentIndex = checkElementIndex(index, values.length, "Index out of bounds for values"); 
    //檢查參數值
    checkArgument(valueToSet <= 100,"Value can't be more than 100"); 
    values[this.currentIndex] = valueToSet;
  }
  public void doOperation(){
    checkState(validateObjectState(),"Can't perform operation");
  }
  private boolean validateObjectState(){
    return this.label.equalsIgnoreCase("open") && values[this.
    currentIndex]==10;
  }
}
下面是對上面例子當中四個方法的摘要信息:
  • checkNotNull(T object, Object message):這個方法若是object不爲null直接返回,若是爲null會拋出空指針異常。
  • checkElementIndex (int index, int size, Object message):在這方法當中,index是你將要訪問的元素下標,size是這個要訪問的array,list或者字符串的長度。而後校驗是否有效,若是無效拋出IndexOutOfBoundsException。
  • checkArgument (Boolean expression, Object message):這方法傳入布爾表達式。 這個布爾表達式若是爲true則繼續執行,不然拋出IllegalArgumentException。
  • checkState (Boolean expression, Object message):這方法傳入 一個布爾表達式涉及對象的狀態,而不是參數。 這個布爾表達式若是爲true則繼續執行,不然拋出IllegalArgumentException。
Object工具
在這個章節當中咱們將介紹幫助檢查null值和建立toString和hashCode的方法的實用方法。咱們接着去看看一個有用的類,它實現Comparable接口。 
 
Getting help with the toString method
當咱們要調試的時候,toString方法是必須重寫的,重寫它的乏味無趣的。然而,Objects類可使用toStringHelper方法讓重寫更簡單。看下面的例子:
public class Book implements Comparable<Book> {
  private Person author;
  private String title;
  private String publisher;
  private String isbn;
  private double price;
  public String toString() {
    return Objects.toStringHelper(this).omitNullValues().add("title", title).add("author", author).add("publisher", publisher)
        .add("price",price).add("isbn", isbn).toString();
  }
}
讓咱們看看toString方法:
  • 首先咱們傳入一個Book對象來建立一個Objects.ToStringHelper實例。
  • 第二步,咱們調用omitNullValues來排除任何null值的屬性。
  • 調用add方法來添加每個屬性的標籤和屬性。
檢查null
firstNonNull方法接受2個參數而且返回第一個參數若是它不爲null。
String value = Objects.firstNonNull(someString, "default value");
firstNonNull方法使用時候若是你不肯定傳入的值是否爲null你能夠提供一個默認值給它。須要注意:若是傳入的2個參數都是null,會拋出空指針異常。
 
建立hash codes
爲類寫hashCode方法是基本的可是乏味無趣。Objects類能夠幫助咱們使用hashCode方法更加簡單。考慮下Book類有4個屬性:title, author, publisher, 和isbn. 下面的代碼將展現使用Object.hashCode方法返回hashCode值。
public int hashCode() {
  return Objects.hashCode(title, author, publisher, isbn);
}
實現CompareTo方法
再次使用Book類,下面是典型的實現compareTo方法。
public int compareTo(Book o) {
  int result = this.title.compareTo(o.getTitle());
  if (result != 0) {
    return result;
  }
  result = this.author.compareTo(o.getAuthor());
  if (result != 0) {
    return result;
  }
  result = this.publisher.compareTo(o.getPublisher());
  if(result !=0 ) {
    return result;
  }
  return this.isbn.compareTo(o.getIsbn());
}
如今讓咱們看一看使用ComparisonChain類來實現compareTo方法:
public int compareTo(Book o) {
  return ComparisonChain.start() .compare(this.title, o.getTitle()) .compare(this.author, o.getAuthor()) 
      .compare(this.publisher, o.getPublisher()) .compare(this.isbn, o.getIsbn()) .compare(this.price, o.getPrice()) .result(); 
}
第二個例子顯得更緊湊和更好閱讀。並且,ComparisonChain類會在第一個比較當中若是返回非零時中止比較,只有一種狀況返回0,那就是全部的比較返回的都是0。
 
概要
在本章中咱們介紹了許多基礎操做。咱們學習瞭如何使用Guava讓分割字符串更簡單經過使用Joiner, Splitter,和很是有用的MapJoiner和MapSplitter類。咱們也學習了使用Charsets, CharMatcher, 和 Strings 來操做字符串。
咱們學習瞭如何讓咱們代碼更增強壯和便於調試經過使用Preconditions類。在Objects類中,咱們學習了一些使用方法來設置默認值和建立toString和hashCode方法。咱們也學習了使用ComparisonChain類來實現compareTo方法更簡單。
在下一章,咱們學習如何使用Guava來進行函數式編程,經過Function和Predicate接口使咱們的編程效率更加高。
相關文章
相關標籤/搜索