掌握了Lambda表達(dá)式和方法引用之后邢享,我們就可以很輕松的 Stream API
了鹏往,沒有掌握Lambda表達(dá)式和方法引用的話,建議去看看我之前寫的文章骇塘。因?yàn)榻酉聛?lái)會(huì)大量的用到它們伊履。
Stream
什么是Stream
?
Stream
是一組用來(lái)處理數(shù)組款违、集合的API唐瀑,它支持順序流和并行流的聚合操作。
Stream特性
- 不是數(shù)據(jù)結(jié)構(gòu)插爹,沒有內(nèi)部存儲(chǔ)哄辣。
- 不支持索引訪問。
- 延遲計(jì)算赠尾。
- 支持并行力穗。
- 很容易生成數(shù)組或集合(
List
、Set
)气嫁。 - 支持過(guò)濾当窗、查找夹纫、轉(zhuǎn)換峡谊、匯總患久、聚合等操作另萤。
備注:
1、在沒有執(zhí)行終止操作之前嘶朱,它是不會(huì)進(jìn)行計(jì)算的蛾坯。
Stream運(yùn)行機(jī)制
Stream
分為數(shù)據(jù)源光酣,中間操作疏遏,終止操作。
數(shù)據(jù)源可以是一個(gè)數(shù)組救军、一個(gè)集合财异、一個(gè)生成器方法、一個(gè)I/O通道等等唱遭。
一個(gè)流可以有零個(gè)或者多個(gè)中間操作戳寸,每一個(gè)中間操作都會(huì)返回一個(gè)新的流,供下一個(gè)操作使用拷泽。一個(gè)流只會(huì)有一個(gè)終止操作疫鹊。
Stream
只有遇到終止操作,它的數(shù)據(jù)源才開始執(zhí)行遍歷操作司致。
Stream常用的API
中間操作
- 過(guò)濾
filter
- 去重
distinct
- 排序
sorted
- 截取
limit
拆吆、skip
- 轉(zhuǎn)換
map
、flatMap
- 其他
peek
終止操作
- 循環(huán)
forEach
- 計(jì)算
min
脂矫、max
枣耀、count
、average
- 匹配
anyMatch
庭再、allMatch
捞奕、noneMatch
、findFirst
拄轻、findAny
- 匯聚
reduce
- 收集器
toArray
颅围、collect
Stream的創(chuàng)建
- 通過(guò)數(shù)組來(lái)創(chuàng)建。
- 通過(guò)集合來(lái)創(chuàng)建恨搓。
- 通過(guò)
Stream.generate
方法來(lái)創(chuàng)建院促。 - 通過(guò)
Stream.iterate
方法來(lái)創(chuàng)建。 - 通過(guò)其他API來(lái)創(chuàng)建(文件奶卓、字符串等等)一疯。
這里說(shuō)明一下,Stream API
在Android使用的話夺姑,要求API 24 以上才能使用墩邀,也可以導(dǎo)入com.annimon:stream:$lastVersion
庫(kù)來(lái)使用它。
了解了一下基本的概念后盏浙,下面我們來(lái)看看Stream API
實(shí)戰(zhàn)眉睹。
Stream實(shí)戰(zhàn)
首先我們看看用代碼如何來(lái)實(shí)現(xiàn)Stream
的創(chuàng)建荔茬。
1、通過(guò)數(shù)組來(lái)創(chuàng)建竹海。
public static<T> Stream<T> of(T... values) {
...
}
String[] arr = {"a", "b", "c", "1", "2"};
Stream<String> stream = Stream.of(arr);
使用Stream.of
方法將數(shù)組作為參數(shù)傳入慕蔚,即可得到 Stream
。
2斋配、通過(guò)集合來(lái)創(chuàng)建孔飒。
default Stream<E> stream() {
...
}
List<String> list = Arrays.asList("a", "b", "c", "1", "2");
Stream<String> stream = list.stream();
使用list.stream
方法創(chuàng)建得到Stream
。
3艰争、通過(guò)Stream.generate
方法來(lái)創(chuàng)建坏瞄。
public static<T> Stream<T> generate(Supplier<T> s) {
...
}
Stream<String> stream = Stream.generate(() -> 1);
使用Stream.generate
方法創(chuàng)建得到Stream
時(shí),傳入的是Supplier
甩卓,它代表一個(gè)輸出鸠匀,說(shuō)明調(diào)用Stream.generate
方法會(huì)不斷的輸入一個(gè)值,放到Stream中逾柿。對(duì)于Stream.generate
方法的使用缀棍,一般都會(huì)和一些中間操作的API搭配使用,比如使用 limit
截取一部分?jǐn)?shù)據(jù)机错。
4爬范、通過(guò)Stream.iterate
方法來(lái)創(chuàng)建。
public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f) {
...
}
Stream<Integer> stream = Stream.iterate(1, x -> x + 1);
使用Stream.iterate
方法創(chuàng)建得到Stream
時(shí)毡熏,傳入的是一個(gè)初始值和一個(gè)一元運(yùn)算(輸入和輸出類型相同)的Lambda表達(dá)式坦敌。上面的操作解釋為輸入一個(gè)初始值為1,每次的輸入結(jié)果為上一次輸出的值加1痢法,然后不然循環(huán)輸入狱窘。
備注:Stream.generate
和 Stream.iterate
方法生成的是一個(gè)無(wú)止盡的 Stream
,我們?cè)谑褂玫臅r(shí)候需要注意這一點(diǎn)财搁。
5蘸炸、通過(guò)其他API來(lái)創(chuàng)建。
String str = "abcd";
IntStream stream = str.chars();
Stream<String> stream = Files.lines(Paths.get("path"));
我們也可以使用一些其他API自帶的創(chuàng)建 Stream
的方法尖奔。
接下來(lái)看看 Stream
常用的API的使用搭儒。
1、中間操作符提茁。
Stream<T> filter(Predicate<? super T> predicate);
Stream<T> distinct();
Stream<T> sorted();
Stream<T> limit(long maxSize);
Stream<T> skip(long n);
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
Stream<T> peek(Consumer<? super T> action);
S parallel();
S sequential();
2淹禾、終止操作符。
void forEach(Consumer<? super T> action);
Optional<T> min(Comparator<? super T> comparator);
Optional<T> max(Comparator<? super T> comparator);
long count();
OptionalDouble average();
boolean anyMatch(Predicate<? super T> predicate);
boolean allMatch(Predicate<? super T> predicate);
boolean noneMatch(Predicate<? super T> predicate);
Optional<T> findFirst();
Optional<T> findAny();
T reduce(T identity, BinaryOperator<T> accumulator);
Object[] toArray();
<R, A> R collect(Collector<? super T, A, R> collector);
看到這些方法是不是很眼熟茴扁?铃岔??
沒錯(cuò)峭火,在講Lambda表達(dá)式和函數(shù)式接口的時(shí)候毁习,我們就列出過(guò)一些函數(shù)式接口的表格智嚷,我再把它貼出來(lái)吧,不是湊字?jǐn)?shù)哦纺且!
接口名 | 參數(shù) | 返回值 | 用途 | 含義 |
---|---|---|---|---|
Predicate | T | boolean | 斷言 | 代表一個(gè)輸入 |
Consumer | T | void | 消費(fèi) | 代表一個(gè)輸入 |
Function<T,R> | T | R | 函數(shù) | 代表一個(gè)輸入盏道,一個(gè)輸出(一般輸入和輸出是不同類型的) |
BiFunction<T,U,R> | (T,U) | R | 函數(shù) | 代表兩個(gè)輸入,一個(gè)輸出(一般輸入和輸出是不同類型的) |
Supplier | None | T | 工廠方法 | 代表一個(gè)輸出 |
UnaryOperator | T | T | 邏輯非载碌、迭代器 | 代表一個(gè)輸入猜嘱,一個(gè)輸出(輸入和輸出是相同類型的) |
BinaryOperator | (T,T) | T | 二元操作 | 代表兩個(gè)輸入,一個(gè)輸出(輸入和輸出是相同類型的) |
不知道有沒有人和我一樣恐仑,第一次學(xué)習(xí)Lambda表達(dá)式的時(shí)候泉坐,根本不理解這些函數(shù)式接口的用途代表什么意思。比如什么是“斷言”裳仆,什么是“消費(fèi)”等等。
所以孤钦,我們還是有必要了解一下這些概念的歧斟,這對(duì)于我們之后的學(xué)習(xí)和學(xué)術(shù)討論很有幫助。
斷言:其實(shí)是防止程序意外出錯(cuò)的一種宏偏形,如果其參數(shù)計(jì)算為假静袖,則程序發(fā)出警告,且退出俊扭。
最常見的用法就是在函數(shù)入口處保證輸入?yún)?shù)的正確性队橙。
assert(object != NULL) ;
消費(fèi):是指程序在運(yùn)行過(guò)程中,資源的消耗和使用萨惑。
最常用的用法就是將元素傳遞給其他函數(shù)處理捐康,并且不給與返回值。
stream.forEach(System.out::println)
函數(shù):也稱函數(shù)關(guān)系式庸蔼,是指給定元素解总,經(jīng)過(guò)一些行為、處理后得到其他的元素的關(guān)系表達(dá)姐仅,它的核心就是對(duì)應(yīng)關(guān)系花枫。
函數(shù)的用途就很廣了,尤其是我們學(xué)習(xí)函數(shù)式編程之后掏膏,才能真正體會(huì)它的強(qiáng)大劳翰。
stream.map {
x -> x.toString
}
工廠方法:我們知道工廠是生成東西的工具,那么我們是不是可以理解為工廠方法是生成方法的工具呢馒疹?是的佳簸,說(shuō)明白一點(diǎn)就是提供一些生成對(duì)象的方法給我們使用,它本身是不創(chuàng)建東西的行冰,我們只需要關(guān)心方法如何使用溺蕉。
stream.collect(Collectors.toList())
可以看到 Collectors
類含有很多生成其他對(duì)象的方法伶丐,比如 toCollection()
、toSet()
疯特、toList()
哗魂、toMap()
等等。
邏輯非:顧名思義漓雅,就是取一個(gè)有效值的反值录别。
使用場(chǎng)景多用于輸入和輸出的類型相同。
Stream.iterate(1, x -> !x)
二元操作:即為輸入兩個(gè)參數(shù)和輸出的類型相同邻吞,多用于合并 Stream
的場(chǎng)景组题。
stream.reduce((value1, value2) ->
value1 + value2
)
了解這些函數(shù)式接口之后,我們?cè)谑褂?Stream API
的時(shí)候就很容易了抱冷,接下來(lái)我們來(lái)看看這些操作符的用法吧崔列。
filter 過(guò)濾
filter中的條件,返回true的才會(huì)保留
// 過(guò)濾基數(shù)值旺遮,保留偶數(shù)值赵讯,最后遍歷
Arrays.asList(1, 2, 3, 4, 5).stream().filter(x -> x%2 == 2).forEach(System.out::println);
distince 去重
distinct是調(diào)用元素的hashCode和equals方法判斷重復(fù)的值或者對(duì)象,從而實(shí)現(xiàn)去重功能耿眉。
對(duì)于實(shí)體類去重時(shí)边翼,需要重寫實(shí)體類的 equals()
以及 hashCode()
方法
也可以使用 filter
、collect
操作符去重鸣剪。
//去重组底,barry存在2個(gè),去重后只有一個(gè)
Stream.of("wally", "barry", "rose", "barry").distinct().forEach(System.out::println);
// filter 實(shí)體類字段去重
private static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
Set<Object> seen = ConcurrentHashMap.newKeySet();
return t -> seen.add(keyExtractor.apply(t));
}
stre.filter(distinctByKey(User::getName)).forEach(System.out::println);
// collect 實(shí)體類字段去重
stream.collect(Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(User::getName))), ArrayList::new)).forEach(System.out::println);
sorted 排序
默認(rèn)按照自然順序筐骇,從小到大排序债鸡。
Stream.of(1, 4, 8, 2, 0, -1).sorted().forEach(System.out::println);
// 倒序排序
Stream.of(1, 4, 8, 2, 0, -1).sorted((a, b) -> b - a).forEach(System.out::println);
limit 限制
限制取出 Stream
的個(gè)數(shù)。
Stream.iterate(1, x -> x + 1).limit(50).forEach(System.out::println);
skip 步長(zhǎng)
跳過(guò)步長(zhǎng)大小取值拥褂。
Stream.iterate(1, x -> x + 1).skip(5).limit(50).forEach(System.out::println);
map 數(shù)據(jù)轉(zhuǎn)換
// 將Stream的每個(gè)元素進(jìn)行大寫轉(zhuǎn)換
Stream.of("wally", "barry", "rose", "barry").map(String.toUpperCase).forEach(System.out::println);
flatMap 攤平
組合多個(gè)流為一個(gè)流
Stream.of(Arrays.asList("wally", "barry", "rose"), Arrays.asList("jack", "ben")).flatMap(names -> names.stream).forEach(System.out::println);
peek 查看
這個(gè)操作符主要目的是用于調(diào)試娘锁,查看 Stream
中的數(shù)據(jù)經(jīng)過(guò)每個(gè)操作符后的狀態(tài)。
Stream.of("wally", "barry", "rose", "barry")
.peek(System.out::println)
.map(String.toUpperCase)
.peek(System.out::println)
.forEach(System.out::println);
需要注意一點(diǎn)饺鹃,peek是中間操作符莫秆,直接使用是無(wú)效的,不會(huì)打印任何東西悔详。
parallel 并行
使用該操作符镊屎,會(huì)把當(dāng)前 Stream
用并行方式處理。
設(shè)置并行 Stream
的線程數(shù)(包含主線程)茄螃。
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "5")
sequential 串行(默認(rèn))
使用該操作符缝驳,會(huì)把當(dāng)前 Stream
用串行方式處理。
forEach 遍歷
Stream.of("wally", "barry", "rose", "barry").forEach(System.out::println);
min 取最小
int min = Stream.of(1, 2, 3, 4, 5, 6).min(Integer::compareTo).orElse(-1);
max 取最大
int max = Stream.of(1, 2, 3, 4, 5, 6).max(Integer::compareTo).orElse(-1);
count 計(jì)數(shù)
統(tǒng)計(jì)元素個(gè)數(shù)
long count = Stream.of(1, 2, 3, 4, 5, 6).count();
average 取平均
取平均只有IntStream、DoubleStream用狱、LongStream中可以使用运怖,也可以使用collect取平均值
Double averagingValue = Stream.of(1, 2, 3, 4, 5, 6).collect(Collectors.averagingInt(value -> value));
anyMatch 找任意匹配條件的值或?qū)ο?/h4>
判斷,只要一個(gè)匹配夏伊,就返回true
// 匹配長(zhǎng)度大于或者等于 5摇展,只要有一個(gè)匹配,即使 rose 的長(zhǎng)度為 4 不符合也返回 true溺忧。
boolean result = Stream.of("wally", "barry", "rose").anyMatch(s -> s.length() >= 5);
allMatch 找所有匹配條件的值或?qū)ο?/h4>
判斷咏连,所有都匹配,才返回true
// 匹配長(zhǎng)度大于或者等于 5鲁森,因?yàn)?rese 的長(zhǎng)度 4祟滴,所以返回false
boolean result = Stream.of("wally", "barry", "rose").allMatch(s -> s.length() >= 5);
noneMatch 找不符合匹配條件的值或?qū)ο?/h4>
// 匹配長(zhǎng)度大于或者等于 5,因?yàn)?wally歌溉、barry 的長(zhǎng)度符合垄懂,所以返回false
boolean result = Stream.of("wally", "barry", "rose").noneMatch(s -> s.length() >= 5);
findFirst 找第一個(gè)值或?qū)ο?/h4>
Optional<Integer> first = Stream.of(1, 2, 3, 4, 5, 6).findFirst();
findAny 找任意的值或?qū)ο?/h4>
Optional<Integer> any = Stream.of(1, 2, 3, 4, 5, 6).findAny();
reduce 累積、結(jié)合
// 匹配長(zhǎng)度大于或者等于 5,因?yàn)?wally歌溉、barry 的長(zhǎng)度符合垄懂,所以返回false
boolean result = Stream.of("wally", "barry", "rose").noneMatch(s -> s.length() >= 5);
Optional<Integer> first = Stream.of(1, 2, 3, 4, 5, 6).findFirst();
findAny 找任意的值或?qū)ο?/h4>
Optional<Integer> any = Stream.of(1, 2, 3, 4, 5, 6).findAny();
reduce 累積、結(jié)合
Optional<Integer> any = Stream.of(1, 2, 3, 4, 5, 6).findAny();
根據(jù)一定的規(guī)則將 Stream
中的元素進(jìn)行計(jì)算后返回一個(gè)唯一的值研底。
有三種方法可以調(diào)用埠偿,分別是:
- Optional<T> reduce(BinaryOperator<T> accumulator);
- T reduce(T identity, BinaryOperator<T> accumulator);
- <U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner);
需要注意的是,在使用三個(gè)參數(shù)的方法時(shí)榜晦,如果 Stream 是非并行的,combiner 不會(huì)生效羽圃,計(jì)算過(guò)程和兩個(gè)參數(shù)的方法是一致的乾胶。
// 方法一,二元運(yùn)算處理朽寞,常用于求和识窿、求最小值或最大值。
Optional<Integer> reduce = Stream.of(1, 2, 3, 4, 5, 6).reduce(Integer::sum);
// 方法二脑融,在方法一的基礎(chǔ)上喻频,增加初始值。常用于將一個(gè) String 類型的 Stream 中的所有元素連接到一起并在最前面添加 value 后返回肘迎。
String reduce = Stream.of("Hello", " ", "World").reduce("Log:", (s1, s2) -> s1+ s2);
// 方法三甥温,并行時(shí),將 identity 參數(shù)與序列中的每一個(gè)元素進(jìn)行 accumulator 轉(zhuǎn)換妓布,得到N個(gè)結(jié)果姻蚓,然后使用 combiner 對(duì)N個(gè)結(jié)果進(jìn)行匯總。
// 使用函數(shù)表示就是:(4+1) * (4+2) * (4+3) = 210;
int reduce = Stream.of(1, 2, 3).reduce(4, (v1, v2) -> v1 + v2, (v1, v2) -> v1 * v2);
reduce即使是并行情況下匣沼,也不會(huì)創(chuàng)建多個(gè)初始化對(duì)象狰挡,combiner接收的兩個(gè)參數(shù)永遠(yuǎn)是同一個(gè)對(duì)象。
// array1 == array2 永遠(yuǎn)為True
Predicate<String> predicate = t -> t.contains("a");
ArrayList<String> list = Stream.of("aa", "ab", "c", "ad").parallel().reduce(new ArrayList<>(),
(array, s) -> {
if (predicate.test(s)) array.add(s);
return array;
},
(array1, array2) -> {
System.out.println(array1 == array2);
array1.addAll(array2);
return array1;
});
toArray 轉(zhuǎn)換成數(shù)組
有兩種方法可以調(diào)用,分別是:
- Object[] toArray();
- <A> A[] toArray(IntFunction<A[]> generator);
Integer[] array = Stream.of(1, 2, 3, 4, 5, 6).toArray(Integer[]::new);
collect 收集
collect含義與reduce有點(diǎn)相似加叁,區(qū)別是reduce即使是并行情況下倦沧,也不會(huì)創(chuàng)建多個(gè)初始化對(duì)象,combiner接收的兩個(gè)參數(shù)永遠(yuǎn)是同一個(gè)對(duì)象它匕。
有兩種方法可以調(diào)用展融,分別是:
- <R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner);
- <R, A> R collect(Collector<? super T, A, R> collector);
// 方法一,和 reduce 的三個(gè)參數(shù)方法用法類似超凳。
Predicate<String> predicate = t -> t.contains("a");
ArrayList<String> list = Stream.of("aa", "ab", "c", "ad").parallel().collect(ArrayList::new,
(array, s) -> {
if (predicate.test(s)) array.add(s);
},
ArrayList::addAll);
// 使用 Collectors 提供的 API
stream.collect(Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(User::getName))), ArrayList::new)).forEach(System.out::println);