Java 8系列之重构和定制收集器
Stream系列:
- Java 8系列之Stream的基本語法詳解
- Java 8系列之Stream的強大工具Collector
- Java 8系列之重構和定制收集器
- Java 8系列之Stream中萬能的reduce?
前面我們已經了解到了Collector類庫中各種收集器的強大,可是,它們也只是能滿足常用的場景。既然開放了Collector接口,我們當然可以根據自已意愿去定制,實際操作起來還是比較簡單的。
Collectors.joining源碼解析
從前面,我們已經了解到一個Collector是由四部分組成的:
- Supplier<A> supplier(): 創建新的結果容器
- BiConsumer<A, T> accumulator(): 將元素添加到結果容器
- BinaryOperator<A> combiner(): 將兩個結果容器合并為一個結果容器
- Function<A, R> finisher(): 對結果容器作相應的變換
我們先看Collectors.joining是怎么實現的:
String strJoin = Stream.of("1", "2", "3", "4").collect(Collectors.joining(",", "[", "]")); System.out.println("strJoin: " + strJoin); // 打印結果 // strJoin: [1,2,3,4]這里,我們跟蹤代碼,看看Collectors.joining的源碼:
public static Collector<CharSequence, ?, String> joining(CharSequence delimiter,CharSequence prefix,CharSequence suffix) {return new CollectorImpl<>(() -> new StringJoiner(delimiter, prefix, suffix),StringJoiner::add, StringJoiner::merge,StringJoiner::toString, CH_NOID); }Collectors.joining實際上返回的是一個CollectorImpl對象,而其是Collector接口的實現類。在創建CollectorImpl對象時,通過方法引用,將StringJoiner的add()、merge()、toString()方法分別傳遞給accumulator()、combiner()及finisher()等四部分。
static class CollectorImpl<T, A, R> implements Collector<T, A, R> {private final Supplier<A> supplier;private final BiConsumer<A, T> accumulator;private final BinaryOperator<A> combiner;private final Function<A, R> finisher;private final Set<Characteristics> characteristics;CollectorImpl(Supplier<A> supplier,BiConsumer<A, T> accumulator,BinaryOperator<A> combiner,Function<A,R> finisher,Set<Characteristics> characteristics) {this.supplier = supplier;this.accumulator = accumulator;this.combiner = combiner;this.finisher = finisher;this.characteristics = characteristics;}CollectorImpl(Supplier<A> supplier,BiConsumer<A, T> accumulator,BinaryOperator<A> combiner,Set<Characteristics> characteristics) {this(supplier, accumulator, combiner, castingIdentity(), characteristics);}@Overridepublic BiConsumer<A, T> accumulator() {return accumulator;}@Overridepublic Supplier<A> supplier() {return supplier;}@Overridepublic BinaryOperator<A> combiner() {return combiner;}@Overridepublic Function<A, R> finisher() {return finisher;}@Overridepublic Set<Characteristics> characteristics() {return characteristics;} }首先要明確的是參數類型,
- 待收集元素的類型:String;
- 累加器的類型:StringCombiner;
- 最終結果的類型:String。
然后,我們一邊閱讀代碼, 一邊看圖, 這樣就能看清Collector到底是怎么工作的。由于Collector可以并行收集,為了可以了解清楚Collector的四部分的作用,我們這里以Collector在兩個容器上并行執行。
Collector的每一個組件都是函數,因此我們使用箭頭表示,Stream中的值用圓圈表示,最終生成的值用橢圓表示。Collector一開始的工作就是創建一個容器。這里我們是實現了Supplier,這是一個工廠方法。
public Supplier<StringJoiner> supplier() {return () -> new StringJoiner(delim, prefix, suffix); }
Collector的accumulator函數的作用就是,它結合之前操作的結果和當前值,生成并返回新的值。 這一邏輯是通過StringJoiner::add方法實現的。
這里的accumulator用來將流中的值疊加入容器中.
combiner方法與reduce方法類似,將兩個容器合并。由于Collector支持并發操作,如果不將多的容器合并,必然會導致數據的混亂。如果僅僅在串行執行,此步驟可以省略。這里,使用了StringJoiner::merge來實現,最后返回的是
public StringJoiner merge(StringJoiner other) {Objects.requireNonNull(other);if (other.value != null) {final int length = other.value.length();、StringBuilder builder = prepareBuilder();builder.append(other.value, other.prefix.length(), length);}return this; }在收集階段,Collector被combiner方法成對合并進一個容器,直到最后只剩一個容器為止.
最后,finisher方法將StringJoiner轉換為最后的結果,將toString方法內聯到方法鏈的末端,這就將 StringCombiners轉換成了我們想要的字符串。
public String toString() {if (value == null) {return emptyValue;} else {if (suffix.equals("")) {return value.toString();} else {int initialLength = value.length();String result = value.append(suffix).toString();// reset value to pre-append initialLengthvalue.setLength(initialLength);return result;}} }這樣,我們就完成了Collector的自定義,好像還有一點我們忽略掉了,那就是Collector的特征。正是忽略了這點,在自定義時,給自己挖了一個坑。關于Characteristics這個Enum看下官方文檔吧,前面已經提到這里不再多述。
Collector自定義起來,也不是特別的麻煩,不過要明確以下幾點:
自定義Collector
現在有個簡單的需求,求一段數字的和,如果是奇數,直接相加;如果是偶數,乘以2后再相加。這樣的場景下,Collector類庫中的收集器不能滿足我們的需求,我們只能夠自己定義了。
1.自定義類作為過渡容器
我們先定義一個類IntegerSum作為過渡容器。這里所說的容器并不一定是集合,只是對數據的臨時存儲,稱之為過渡容器。在IntegerSum類內,定義了3個方法:
- doSum:作為累加器,實現求和操作
- doCombine:作為combine,將兩個容器合并
- toValue:作為finisher,將IntegerSum轉為所需要的結果Integer
明確參數類型
- 待收集元素的類型:Integer
- 累加器的類型:IntegerSum
- 最終結果的類型:Integer
實現Collector接口
Integer integerSum = Stream.of(1, 2, 3, 4).collect(new Collector<Integer, IntegerSum, Integer>() {@Overridepublic Supplier<IntegerSum> supplier() {return () -> new IntegerSum(2);}@Overridepublic BiConsumer<IntegerSum, Integer> accumulator() {return IntegerSum::doSum;}@Overridepublic BinaryOperator<IntegerSum> combiner() {return IntegerSum::doCombine;}@Overridepublic Function<IntegerSum, Integer> finisher() {return IntegerSum::toValue;}@Overridepublic Set<Characteristics> characteristics() {Set<Collector.Characteristics> CH_NOID = Collections.emptySet();return CH_NOID;}}); System.out.println("integerSum: " + integerSum); // 打印結果:integerSum: 18在實現Collector接口時,我們通過方法引用的方式,指定了Collector的四部分的實現形式,見代碼。對于Characteristics,并未對Collecotor設置特征。
這樣一個簡單的自定義Collector,就實現了。如果有興趣,你可以試一下。
---------------------?
作者:行云間?
來源:CSDN?
原文:https://blog.csdn.net/io_field/article/details/54971555?
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!
總結
以上是生活随笔為你收集整理的Java 8系列之重构和定制收集器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java 8系列之Stream的强大工具
- 下一篇: Java 8系列之Stream中万能的r