[Guava原始碼日報](3)Joiner分析
把任意的字串,通過一些分隔符將它們連線起來是大多數程式設計師經常處理東西。以前的方式就是迭代,append等操作,使用Joiner可以更方便。
我們先看一下以前的處理方式:
// 通過分隔符將字串連結在一起
public static String builder(List<String> list,String delimiter){
StringBuilder stringBuilder = new StringBuilder();
for(String str : list){
if(str != null){
stringBuilder.append (str).append(delimiter);
}//if
}//for
stringBuilder.setLength(stringBuilder.length() - delimiter.length());
return stringBuilder.toString();
}
這樣操作顯得比較麻煩,為此Joiner為我們提供了很好的解決方法。
舉例:
package com.qunar.guava.joiner;
import com.google.common.base.Joiner;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* Created by xiaosi on 16-3-2.
*/
public class Test {
private static List<String> list = new ArrayList<String>();
private static String delimiter = "---";
public static void main(String[] args) {
list.add("apple");
list.add ("banana");
list.add(null);
list.add("pear");
test1();
test2();
test3();
test4();
test5();
test6();
}
// 通過分隔符將字串連結在一起
public static String builder(List<String> list,String delimiter){
StringBuilder stringBuilder = new StringBuilder();
for(String str : list){
if(str != null){
stringBuilder.append(str).append(delimiter);
}//if
}//for
stringBuilder.setLength(stringBuilder.length() - delimiter.length());
return stringBuilder.toString();
}
public static void test1(){
System.out.println("test1:" + builder(list,delimiter));
}
// skipNulls
public static void test2(){
Joiner joiner = Joiner.on(delimiter);
String excludeNUllString = joiner.skipNulls().join(list);
System.out.println("test2:" + excludeNUllString);
}
// useForNull
public static void test3(){
Joiner joiner = Joiner.on(delimiter);
String str = joiner.useForNull("invalid fruit").join(list);
System.out.println("test3:" + str);
}
// passing a StringBuilder instance to the Joiner class and the StringBuilder object is returned.
public static void test4(){
Joiner joiner = Joiner.on(delimiter);
StringBuilder stringBuilder = new StringBuilder(" fruit:");
joiner.skipNulls().appendTo(stringBuilder,list);
System.out.println("test4:" + stringBuilder.toString());
}
// map
public static void test5(){
HashMap<String,Float> map = new HashMap<String, Float>();
map.put("apple",5.6F);
map.put("pear",4.5F);
map.put("banana",7.8F);
Joiner.MapJoiner mapJoiner = Joiner.on(",").withKeyValueSeparator("=");
String str = mapJoiner.join(map);
System.out.println("test5:" + str);
}
// 錯誤方法
public static void test6(){
Joiner joiner = Joiner.on(delimiter);
String str = joiner.useForNull("invalid fruit").useForNull(".....").join(list);
System.out.println("test6:" + str);
}
}
執行結果:
test1:apple---banana---pear
test2:apple---banana---pear
test3:apple---banana---invalid fruit---pear
test4: fruit:apple---banana---pear
test5:banana=7.8,apple=5.6,pear=4.5
Exception in thread "main" java.lang.UnsupportedOperationException: already specified useForNull
at com.google.common.base.Joiner$1.useForNull(Joiner.java:233)
at com.qunar.guava.joiner.Test.test6(Test.java:76)
at com.qunar.guava.joiner.Test.main(Test.java:27)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)
2. 原始碼分析
2.1 構造器
/*
* 字串分隔符構造Joiner物件
* @param separator 字串分隔符
*/
private Joiner(String separator) {
this.separator = checkNotNull(separator);
}
/**
* 使用Joiner物件構造Joiner物件
* @param prototype Joiner物件
*/
private Joiner(Joiner prototype) {
this.separator = prototype.separator;
}
這裡構造器都是private級別的,所以我們自己不能直接使用new建立Joiner物件,這裡的構造器是為類中其他方法提供的。
2.2 設定分隔符
這裡有兩種方式設定分隔符,一種給它傳遞一個字串形式的分隔符,另一種是給它傳遞字元形式的分隔符
/**
* 設定分隔符
* @param separator 分隔符(字串)
* @return 返回Joiner物件
*/
public static Joiner on(String separator) {
return new Joiner(separator);
}
/**
* 設定分隔符
* @param separator 分隔符(字元)
* @return
*/
public static Joiner on(char separator) {
// 使用字元轉換未字串
return new Joiner(String.valueOf(separator));
}
在這我們就用到了上面所說的私有構造器。
2.3 toString方法
/**
* 轉換為字元序列
* @param part Object物件
* @return 返回字元序列
*/
CharSequence toString(Object part) {
// 先決條件 判斷是否為null
checkNotNull(part);
// 如果part是CharSequence型別的例項物件則返回CharSequence物件 否則轉換為String型別返回
return (part instanceof CharSequence) ? (CharSequence) part : part.toString();
}
如果part是CharSequence型別的例項物件則返回CharSequence物件 否則轉換為String型別返回。
備註:
CharSequence是一個介面,它只包括length(), charAt(int index), subSequence(int start, int end)這幾個API介面。除了String實現了CharSequence之外,StringBuffer和StringBuilder也實現了CharSequence介面。
2.4 useForNull
用給定的字串替換出現的null值。它是通過返回一個覆蓋了方法的 Joiner 例項來實現的。
/**
* 使用nullText代替出現的null
* @param nullText 替換的字串
* @return 返回替換後的一個新物件
*/
@CheckReturnValue
public Joiner useForNull(final String nullText) {
// 先決條件 判斷是否為null
checkNotNull(nullText);
// 使用內部類返回Joiner物件
return new Joiner(this) {
// 重寫一下幾個方法
@Override
// 如果引數為null,則用nullText代替返回,否則轉換為字元序列直接返回
CharSequence toString(@Nullable Object part) {
return (part == null) ? nullText : Joiner.this.toString(part);
}
@Override
// 第一次呼叫useForNull會返回一個新物件,再次呼叫useForNull時則是呼叫這個內部類中的方法,丟擲異常
public Joiner useForNull(String nullText) {
throw new UnsupportedOperationException("already specified useForNull");
}
@Override
// 第一次呼叫useForNull會返回一個新物件,再次呼叫skipNulls時則是呼叫這個內部類中的方法,丟擲異常
public Joiner skipNulls() {
throw new UnsupportedOperationException("already specified useForNull");
}
};
}
使用useForNull方法,會產生一個新的Joiner物件,並且重寫了toString(),useForNull()和SkipNulls()方法。所以再次呼叫以上幾個方法時,不是呼叫的Joiner原始方法,而是呼叫返回的內部類重寫的方法。為了防止重複呼叫 useForNull 和 skipNulls,還特意覆蓋了這兩個方法,一旦呼叫就丟擲執行時異常。為什麼不能重複呼叫 useForNull ?因為覆蓋了 toString 方法,而覆蓋實現中需要呼叫覆蓋前的 toString。
我們舉個例子:
public static void test6(){
Joiner joiner = Joiner.on(delimiter);
String str = joiner.useForNull("invalid fruit").useForNull(".....").join(list);
System.out.println("test6:" + str);
}
我們連續呼叫兩次useForNull()方法,則會丟擲內部類中定義的UnsupportedOperationException,並且提示already specified useForNull錯誤資訊。我們想想也就明白,我們都已經用useForNull方法處理null了,再次呼叫useForNull()和skipNulls()方法已經沒有任何意義,所以重新返回一個物件,並重新這幾個方法。
2.5 appendTo(A appendable,Iterator<?> parts)
對parts進行分割並使用分隔符進行連線,最後新增到appendable內容後。
只要實現了Appendable介面的實現類都可以使用appendTo方法。
// A是繼承Appendable介面的一個介面
public <A extends Appendable> A appendTo(A appendable, Iterator<?> parts) throws IOException {
checkNotNull(appendable);
// 遍歷迭代器
if (parts.hasNext()) {
// 新增到appendable後面
appendable.append(toString(parts.next()));
// 遍歷迭代器
while (parts.hasNext()) {
// 使用分隔符分割
appendable.append(separator);
// 新增到appendable後面
appendable.append(toString(parts.next()));
}
}
return appendable;
}
2.6 skipNulls
跳過null值,不用處理,如果向處理過程中的null值,可以使用useForNull方法處理null值。
@CheckReturnValue
public Joiner skipNulls() {
// 使用內部類返回Joiner物件
return new Joiner(this) {
@Override
public <A extends Appendable> A appendTo(A appendable, Iterator<?> parts) throws IOException {
checkNotNull(appendable, "appendable");
checkNotNull(parts, "parts");
// 找到第一個不為null的Object物件
while (parts.hasNext()) {
Object part = parts.next();
if (part != null) {
appendable.append(Joiner.this.toString(part));
break;
}
}
// 使用分隔符連線,並新增到appendable實現類內容末尾
while (parts.hasNext()) {
Object part = parts.next();
if (part != null) {
appendable.append(separator);
appendable.append(Joiner.this.toString(part));
}
}
return appendable;
}
@Override
// 第一次呼叫skipNulls會返回一個新物件,再次呼叫useForNull時則是呼叫這個內部類中的方法,丟擲異常
public Joiner useForNull(String nullText) {
throw new UnsupportedOperationException("already specified skipNulls");
}
@Override
// 第一次呼叫skipNulls會返回一個新物件,再次呼叫withKeyValueSeparator時則是呼叫這個內部類中的方法,丟擲異常
public MapJoiner withKeyValueSeparator(String kvs) {
throw new UnsupportedOperationException("can't use .skipNulls() with maps");
}
};
}
2.7 拼接鍵值對
MapJoiner 實現為 Joiner 的一個靜態內部類,它的建構函式和 Joiner 一樣也是私有,只能通過 Joiner#withKeyValueSeparator 來生成例項。類似地,MapJoiner 也實現了 appendTo 方法和一系列的過載,還用 join 方法對 appendTo 做了封裝。MapJoiner 整個實現和 Joiner 大同小異,在實現中大量 Joiner 的 toString 方法來保證空指標保護行為和初始化時的語義一致。
MapJoiner 也實現了一個 useForNull 方法,這樣的好處是,在獲取 MapJoiner 之後再去設定空指標保護,和獲取 MapJoiner 之前就設定空指標保護,是等價的,使用者無需去關心順序問題。