天天看點

[Guava源碼日報](3)Joiner分析

版權聲明:本文為部落客原創文章,未經部落客允許不得轉載。 https://blog.csdn.net/SunnyYoona/article/details/71079070

把任意的字元串,通過一些分隔符将它們連接配接起來是大多數程式員經常處理東西。以前的方式就是疊代,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)           

https://note.youdao.com/md/?file=%2Fyws%2Fapi%2Fpersonal%2Ffile%2FWEB056965b5a0ed8daccb3b4e2c197c7005%3Fmethod%3Ddownload%26read%3Dtrue#2-%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90 2. 源碼分析

https://note.youdao.com/md/?file=%2Fyws%2Fapi%2Fpersonal%2Ffile%2FWEB056965b5a0ed8daccb3b4e2c197c7005%3Fmethod%3Ddownload%26read%3Dtrue#21-%E6%9E%84%E9%80%A0%E5%99%A8 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對象,這裡的構造器是為類中其他方法提供的。

https://note.youdao.com/md/?file=%2Fyws%2Fapi%2Fpersonal%2Ffile%2FWEB056965b5a0ed8daccb3b4e2c197c7005%3Fmethod%3Ddownload%26read%3Dtrue#22-%E8%AE%BE%E7%BD%AE%E5%88%86%E9%9A%94%E7%AC%A6 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));
    }           

在這我們就用到了上面所說的私有構造器。

https://note.youdao.com/md/?file=%2Fyws%2Fapi%2Fpersonal%2Ffile%2FWEB056965b5a0ed8daccb3b4e2c197c7005%3Fmethod%3Ddownload%26read%3Dtrue#23-tostring%E6%96%B9%E6%B3%95 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接口。

https://note.youdao.com/md/?file=%2Fyws%2Fapi%2Fpersonal%2Ffile%2FWEB056965b5a0ed8daccb3b4e2c197c7005%3Fmethod%3Ddownload%26read%3Dtrue#24-usefornull 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()方法已經沒有任何意義,是以重新傳回一個對象,并重新這幾個方法。

https://note.youdao.com/md/?file=%2Fyws%2Fapi%2Fpersonal%2Ffile%2FWEB056965b5a0ed8daccb3b4e2c197c7005%3Fmethod%3Ddownload%26read%3Dtrue#25-appendtoa-appendableiterator-parts 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;
    }           

https://note.youdao.com/md/?file=%2Fyws%2Fapi%2Fpersonal%2Ffile%2FWEB056965b5a0ed8daccb3b4e2c197c7005%3Fmethod%3Ddownload%26read%3Dtrue#26-skipnulls 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");
            }
        };
    }           

https://note.youdao.com/md/?file=%2Fyws%2Fapi%2Fpersonal%2Ffile%2FWEB056965b5a0ed8daccb3b4e2c197c7005%3Fmethod%3Ddownload%26read%3Dtrue#27-%E6%8B%BC%E6%8E%A5%E9%94%AE%E5%80%BC%E5%AF%B9 2.7 拼接鍵值對

MapJoiner 實作為 Joiner 的一個靜态内部類,它的構造函數和 Joiner 一樣也是私有,隻能通過 Joiner#withKeyValueSeparator 來生成執行個體。類似地,MapJoiner 也實作了 appendTo 方法和一系列的重載,還用 join 方法對 appendTo 做了封裝。MapJoiner 整個實作和 Joiner 大同小異,在實作中大量 Joiner 的 toString 方法來保證空指針保護行為和初始化時的語義一緻。

MapJoiner 也實作了一個 useForNull 方法,這樣的好處是,在擷取 MapJoiner 之後再去設定空指針保護,和擷取 MapJoiner 之前就設定空指針保護,是等價的,使用者無需去關心順序問題。

繼續閱讀