實習前只是粗略的看了下Java8的一些基本語法,但是沒有系統的學習過.在使用一段時間後決定系統的對其進行一次分析,加深對Java8函數語言程式設計的理解,提高自己的編碼技巧.另外kotlin崛起,感興趣的朋友嘗試下混編也未嘗不可.
函式式介面
函式式介面,對於Java來說就是介面內只有一個公開方法的介面,因為使用lanbda表示式,例如() -> user.getName()
對應的呼叫則可能是func.get()
,編譯器會根據介面推斷所屬於的方法,如果有兩個則無法推斷.Java8提供了很多函式式介面,一般都使用註解@FunctionalInterface
宣告,有必要了解如下一些函式式介面.
函式式介面 | 引數型別 | 返回型別 | 描述 |
---|---|---|---|
Supplier | 無 | T | 接收一個T型別的值 |
Consumer | T | 無 | 處理一個T型別的值 |
BiConsumer | T,U | 無 | 處理T型別和U型別的值 |
Predicate | T | boolean | 處理T型別的值,並返回true或者false. |
ToIntFunction | T | int | 處理T型別的值,並返回int值 |
ToLongFunction | T | long | 處理T型別的值,並返回long值 |
ToDoubleFunction | T | double | 處理T型別的值,並返回double值 |
Function | T | R | 處理T型別的值,並返回R型別值 |
BiFunction | T,U | R | 處理T型別和U型別的值,並返回R型別值 |
BiFunction | T,U | R | 處理T型別和U型別的值,並返回R型別值 |
UnaryOperator | T | T | 處理T型別值,並返回T型別值, |
BinaryOperator | T,T | T | 處理T型別值,並返回T型別值 |
以上的函式每一個代表的都是一種基本的操作,操作之間可以自由組合,所以才有了stream這些靈活的操作.
Stream操作
Stream的操作是建立在函式式介面的組合上的,最好的學習方法是看Stream介面來學習.下面舉一些例子來分析,假設有這樣的一些初始資料.
List<String> testData = new ArrayList<String>();
testData.add("張三");
testData.add("李四");
testData.add("王二");
testData.add("麻子");複製程式碼
filter
Stream<T> filter(Predicate<? super T> predicate);複製程式碼
filter接收predicate函式,predicate是接收T值,返回boolean值,那麼對應的引用就可以寫成如下形式,意思是取集合中以`張`開頭的名字.
testData.stream()
.filter(x -> x.startsWith("張"))複製程式碼
map
<R> Stream<R> map(Function<? super T, ? extends R> mapper);複製程式碼
map操作接收的是Function介面,對於Function接收T值返回R值,那map的作用就很明顯是轉換用的,比如下面程式碼,轉換名稱為對應的名稱長度,也就是從輸入String資料返回int資料.
testData.stream()
.map(x -> x.length())複製程式碼
flatMap
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);複製程式碼
flatMap和map都是使用Function介面,不同的是返回值flatMap限定為Stream型別.所以flatMap可以作為合併流使用,如以下程式碼,提取出所有的字元.
testData.stream()
.flatMap(x -> Stream.of(x.split("")))
.collect(Collectors.toList());
//輸出 [張, 三, 李, 四, 王, 二, 麻, 子]複製程式碼
peek
Stream<T> peek(Consumer<? super T> action);複製程式碼
peek引數為Consumer,Consumer接收T值,無返回,那麼該方法就可以作為除錯不影響stream中內容的一些操作,不過由於物件都是地址引用,你再此做一些物件內容操作也是可以的.
reduce
<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner);複製程式碼
Reduce比較複雜的一個介面,屬於歸納性操作,看引數,第一個是U泛型,也就是輸入型別的引數,最為初始值,第二個BiFunction,接收T,U引數,返回U型別引數,BinaryOperator接收U,U型別,並返回U型別.
StringBuilder identity = new StringBuilder();
StringBuilder reduce = testData.stream()
.flatMap(x -> Stream.of(x.split("")))
.reduce(identity, (r, x) -> {
r.append(x);
return r;
}, StringBuilder::append);
System.out.println(identity == reduce);
System.out.println(reduce.toString());
//輸出 true
// 張三李四王二麻子複製程式碼
首先提供一個基本容器identity,然後兩個引數r即是identity,x為每次輸入引數,最後一個StringBuilder::append是併發下多個identity的合併策略.
再舉個例子,既然reduce屬於歸納性操作,那麼也可以當成collect使用,如下:
ArrayList<String> identity = new ArrayList<>();
ArrayList<String> result = testData.stream()
.flatMap(x -> Stream.of(x.split("")))
.reduce(identity, (r, x) -> {
r.add(x);
return r;
},(r1,r2) -> {
r1.addAll(r2);
return r1;
});
System.out.println(identity == result);
System.out.println(result);
//輸出 true
//[張, 三, 李, 四, 王, 二, 麻, 子]複製程式碼
強大的collect
collect無疑是stream中最強大的操作,掌握了collect操作才能說掌握了stream.為了便於使用者,Java提供了Collectors
類,該類提供了很多便捷的collect操作,如Collector<T, ?, List<T>> toList()
,Collector<T, ?, Set<T>> toSet()
等操作.這些操作最終都會呼叫如下建構函式構造出collector物件,因此掌握該本質是最佳的學習方式.
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;
}複製程式碼
Supplier類似reduce中的u,接收一個後設資料,BiConsumer則是運算元據,BinaryOperator併發下聚合,finisher完成時的轉換操作,Set應該按照定義是優化一些操作中的轉換.如下面的toList()操作,其finish操作為castingIdentity()
.
public static <T>
Collector<T, ?, List<T>> toList() {
return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,
(left, right) -> { left.addAll(right); return left; },
CH_ID);
}複製程式碼
再看toMap的實現
public static <T, K, U, M extends Map<K, U>>
Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction,
Supplier<M> mapSupplier) {
BiConsumer<M, T> accumulator
= (map, element) -> map.merge(keyMapper.apply(element),
valueMapper.apply(element), mergeFunction);
return new CollectorImpl<>(mapSupplier, accumulator, mapMerger(mergeFunction), CH_ID);
}複製程式碼
Function作為轉換函式提供了key和value的轉換,BinaryOperator提供了重複key合併策略,mapSupplier則表示最終收集到的容器.那麼使用就很簡單了
HashMap<Character, String> map = testData.stream()
.collect(Collectors.toMap(x -> x.charAt(0), Function.identity()
, (v1, v2) -> v2, HashMap::new));複製程式碼
其他還有很多方法,就不一一敘述,主要是瞭解這些介面,知道他所擁有的功能,以及組合的意義,即可很好的掌握Java中的函數語言程式設計.
個人部落格 mrdear.cn ,歡迎交流