Java Stream(流) api介紹
流是Java API的新成員畴蒲,它允許你以聲明性方式處理數(shù)據(jù)集合(通過查詢語句來表達咕宿,而不
是臨時編寫一個實現(xiàn))烂翰。就現(xiàn)在來說,你可以把它們看成遍歷數(shù)據(jù)集的高級迭代器休溶。
1.篩選瘪阁、切片 filter、distinct邮偎、limit管跺、skip
//filter 篩選
List<Integer> collect = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9)
.filter(x -> x > 6)
.collect(Collectors.toList());
//distinct 去重
List<Integer> collect = Stream.of(1, 2, 3, 5, 5, 6, 7, 8, 9)
.distinct()
.collect(Collectors.toList());
//limit 截取
List<Integer> collect = Stream.of(1, 2, 3, 5, 5, 6, 7, 8, 9)
.limit(4)
.collect(Collectors.toList());
//skip 跳過
List<Integer> collect = Stream.of(1, 2, 3, 5, 5, 6, 7, 8, 9)
.skip(4)
.collect(Collectors.toList());
//可以使用 limit 和 skip對集合進行分頁
int pageNo = 2;
int pageSize = 5;
List<Integer> collect = Stream.of(1, 2, 3, 5, 5, 6, 7, 8, 9)
.skip((pageNo - 1) * pageSize)
.limit(pageSize).collect(Collectors.toList());
//peek
List<Integer> collect = Stream.of(1, 2, 3, 5, 5, 6, 7, 8, 9)
.peek(x-> System.out.println(x))
.collect(Collectors.toList());
2.映射 map、flatmap
map可以把一個對象映射成另一個不同的對象
//map 把 Integer 映射成了 String
List<String> collect = Stream.of(1, 2, 3, 5, 5, 6, 7, 8, 9).map(x -> String.valueOf(x)).collect(Collectors.toList());
flatmap 扁平流 可以把不同層次的流合成一個流
//建立一個對象
class Trade{
private String tid;
//包含另一個對象的列表
private List<Order> orders;
public List<Order> getOrders() {
return orders;
}
public void setOrders(List<Order> orders) {
this.orders = orders;
}
public String getTid() {
return tid;
}
public void setTid(String tid) {
this.tid = tid;
}
}
class Order{
private String oid;
public Order(String oid) {
this.oid = oid;
}
public String getOid() {
return oid;
}
public void setOid(String oid) {
this.oid = oid;
}
@Override
public String toString() {
return "Order{" +
"oid='" + oid + '\'' +
'}';
}
}
public static void main(String[] args) {
Trade trade = new Trade();
ArrayList<Order> orders = new ArrayList<>();
orders.add(new Order("order1"));
orders.add(new Order("order2"));
trade.setTid("trade");
trade.setOrders(orders);
Trade trade2 = new Trade();
ArrayList<Order> orders2 = new ArrayList<>();
orders2.add(new Order("order3"));
orders2.add(new Order("order4"));
trade2.setTid("trade2");
trade2.setOrders(orders2);
ArrayList<Trade> trades = new ArrayList<>();
trades.add(trade);
trades.add(trade2);
//flatMap入?yún)魅攵鄠€流組合成一個
List<Order> collect = trades.stream().flatMap(x -> x.getOrders().stream()).collect(Collectors.toList());
System.out.println(collect);
}
//輸出 order 對象組成的集合
>> [Order{oid='order1'}, Order{oid='order2'}, Order{oid='order3'}, Order{oid='order4'}]
//還可以用扁平流拆分單詞成單獨的字母
Stream.of("add", "one").flatMap(x -> Arrays.stream(x.split(""))).collect(Collectors.toList());
3.查找禾进、匹配(終端) allMatch豁跑、anyMatch、noneMatch泻云、findFirst艇拍、findAny
//anyMatch 流中是否有一個元素能匹配給定的謂詞
Stream.of(1, 2, 3, 5, 5, 6, 7, 8, 9).anyMatch(x -> x == 4)
//allMatch 能不能匹配所有得元素
Stream.of(1, 2, 3, 5, 5, 6, 7, 8, 9).allMatch(x -> x < 1000);
//noneMatch 確保流中沒有任何元素與給定的謂詞匹配
Stream.of(1, 2, 3, 5, 5, 6, 7, 8, 9).noneMatch(x -> x < 1000);
//findAny findAny方法將返回當前流中的任意元素 在利用短路找到結果時立即結束
Stream.of(1, 2, 3, 5, 5, 6, 7, 8, 9).filter(x -> x < 1000).findAny();
//findFirst 方法將返回當前流中的第一個元素
Stream.of(1, 2, 3, 5, 5, 6, 7, 8, 9).filter(x -> x < 1000).findFirst();
以上操作有些回返回Optional對象
Optional里面幾種可以迫使你顯式地檢查值是否存在或處理值不存在狐蜕,使用Optional可以避免和null檢查相關的bug。
isPresent()將在Optional包含值的時候返回true, 否則返回false卸夕。
ifPresent(Consumer block)會在值存在的時候執(zhí)行給定的代碼塊层释。
T get()會在值存在時返回值,否則拋出一個NoSuchElement異常快集。
T orElse(T other)會在值存在時返回值贡羔,否則返回一個默認值。
4.歸約(終端)reduce
使用reduce操作來表達更復雜的查詢个初,此類查詢需要將流中所有元素反復結合起來乖寒,得到一個值,比如一個Integer院溺。
//歸約求和
//沒有初始值的情況返回一個 Optional
Stream.of(1, 2, 3, 5, 5, 6, 7, 8, 9).reduce((x, y) -> x + y);
//有初始值就直接返回一個數(shù)值
Stream.of(1, 2, 3, 5, 5, 6, 7, 8, 9).reduce(0,(x, y) -> x + y);
//找最大或者最小值
Stream.of(1, 2, 3, 5, 5, 6, 7, 8, 9).reduce(0,(x, y) -> x < y ? x : y)
//或者
Stream.of(1, 2, 3, 5, 5, 6, 7, 8, 9).reduce(Integer::min);
廣義的歸約
//還沒看懂楣嘁,跟并發(fā)有關
Stream.of(1, 2, 3, 5, 5, 6, 7, 8, 9).parallel().reduce(5, (x, y) -> x+y, (x, y) -> x);
5.數(shù)值流
如果 Stream.of(1, 2, 3, 5, 5, 6, 7, 8, 9).reduce(Integer::sum); 語句中的數(shù)據(jù)類型是Integer,那么計算總和的時候里面暗含著裝箱成本珍逸,每個Integer都必須拆箱成一個原始類型逐虚, 再進行求和。為了解決這個問題谆膳,java8引入了三個原始類型特化流來解決這個問題痊班,分別是IntStream,DoubleStream摹量,LongStream
//該方法將會返回一個IntStream的原始類型的流,在操作計算就不會有裝箱成本了馒胆,DubleStream缨称,LongStream也是類似
IntStream intStream = Stream.of(1, 2, 3, 5, 5, 6, 7, 8, 9).mapToInt(x -> x);
//原始類型流特化計算
Stream.of(1, 2, 3, 5, 5, 6, 7, 8, 9).mapToInt(x->x).sum();
6.構建流
除了用集合生成流,還有如下其他方式生成
6.1 由值創(chuàng)建流
你可以使用靜態(tài)方法Stream.of祝迂,通過顯式值創(chuàng)建一個流睦尽。它可以接受任意數(shù)量的參數(shù)。例如型雳,以下代碼直接使用Stream.of創(chuàng)建了一個字符串流
//把字符串轉(zhuǎn)大寫当凡,并打印出來
Stream<String> stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action");
stream.map(String::toUpperCase).forEach(System.out::println);
// 可以使用empty得到一個空流
Stream<String> emptyStream = Stream.empty();
6.2 數(shù)組創(chuàng)建流
你可以使用靜態(tài)方法Arrays.stream從數(shù)組創(chuàng)建一個流。它接受一個數(shù)組作為參數(shù)纠俭。例如沿量, 你可以將一個原始類型int的數(shù)組轉(zhuǎn)換成一個IntStream
int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers).sum();
6.1 文件流
//java8 io支持從文件讀取數(shù)據(jù)生成流,下面可以查詢讀取文件里有多少個不同的單詞
long uniqueWords = 0;
try(Stream<String> lines = Files.lines(Paths.get("d://test.txt"), Charset.defaultCharset())){
uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" ")))
.distinct()
.count();
} catch(IOException e){
}
6.1 無限流
可以使用函數(shù)創(chuàng)建無限流冤荆,Stream API提供了兩個靜態(tài)方法來從函數(shù)生成流:Stream.iterate和Stream.generate朴则。
//生成一個無窮的偶數(shù)列表,iterate是可以根據(jù)上一個參數(shù)不斷進行迭代
Stream.iterate(0, n -> n + 2).limit(10).forEach(System.out::println);
//生成一個無窮的隨機數(shù)列表钓简,與iterate不一樣的是乌妒,函數(shù)是不需要入?yún)⒌男谙耄恳粋€都是有方法生成
Stream.generate(Math::random).limit(5).forEach(System.out::println);
7.收集器和高級歸約
collect 不僅僅是用來把Stream中所有的元素結合成一個List,還是一個歸約操作撤蚊,就像reduce一樣可以接 受各種做法作為參數(shù)古掏,將流中的元素累積成一個匯總結果。
collect 是歸約操作
collector 收集器接口
collectors 是java內(nèi)置的已經(jīng)實現(xiàn)的collector接口的工具類方法集合侦啸,提供了很多靜態(tài)工廠方法
7.1 匯總
//返回數(shù)據(jù)元素的個數(shù)
Stream.of(1, 2, 3, 5, 5, 6, 7, 8, 9).collect(Collectors.counting()); >> 9
//返回最小值槽唾,需要傳入一個比較器,返回的是一個Option匹中,防止stream為空時拋出異常
Stream.of(1, 2, 3, 5, 5, 6, 7, 8, 9).collect(Collectors.minBy(Comparable::compareTo)); >> 1
//返回最大值夏漱,其他同上
Stream.of(1, 2, 3, 5, 5, 6, 7, 8, 9).collect(Collectors.MaxBy(Comparable::compareTo)); >> 9
//求和,同類型的 還有 summigLong summingDouble
Stream.of(1, 2, 3, 5, 5, 6, 7, 8, 9).collect(Collectors.summingInt(x -> x));
//平均數(shù)顶捷,不存在值時返回0 同樣還有 averagingLong averagingDouble
Stream.of(1, 2, 3, 5, 5, 6, 7, 8, 9).collect(Collectors.averagingInt(x -> x));
//還有一個更強大的收集器挂绰,Collectors.summarizingInt 返回一個IntSummaryStatistics對象,打印出來如下,返回當前數(shù)據(jù)的總個數(shù)服赎,和葵蒂,最小值,最大值和平均數(shù)
//同樣的還有 summarizingDouble summarizingDouble
//>> IntSummaryStatistics{count=9, sum=46, min=1, average=5.111111, max=9}
IntSummaryStatistics collect = Stream.of(1, 2, 3, 5, 5, 6, 7, 8, 9).collect(Collectors.summarizingInt(x -> x));
連接字符串
//可以把字符串連接起來
Stream.of(1, 2, 3, 5, 5, 6, 7, 8, 9).map(String::valueOf).collect(Collectors.joining()); >> 123556789
//一個參數(shù)可以指定連接符
Stream.of(1, 2, 3, 5, 5, 6, 7, 8, 9).map(String::valueOf).collect(Collectors.joining("-")); >> 1-2-3-5-5-6-7-8-9
//三個參數(shù)可以指定連接符和前綴后綴
Stream.of(1, 2, 3, 5, 5, 6, 7, 8, 9).map(String::valueOf).collect(Collectors.joining("-","@","&")); >> @1-2-3-5-5-6-7-8-9&
7.2分組
可以用Collectors.groupingBy工廠方法返回的收集器對數(shù)據(jù)進行分組重虑,groupingBy也可以和其他收集器合起來實現(xiàn)一些非常復雜的效果
//使用trade作為例子
Trade trade = new Trade();
trade.setTid("trade");
Trade trade2 = new Trade();
trade2.setTid("trade2");
Trade trade3 = new Trade();
trade3.setTid("trade");
ArrayList<Trade> trades = new ArrayList<>();
trades.add(trade);
trades.add(trade2);
trades.add(trade3);
// groupingBy 會把集合按tid進行歸約 groupingBy可以生成一個map
Map<String, List<Trade>> collect1 = trades.stream().collect(Collectors.groupingBy(Trade::getTid));
//可以傳入第二個分組函數(shù)進行二級分組践付,理論上還可以一直嵌套下去進行多級分組
Map<String, Map<String, List<Trade>>> collect = trades.stream().collect(Collectors.groupingBy(Trade::getTid, Collectors.groupingBy(Trade::getTid)));
//還可以聯(lián)合其他收集器一起使用
trades.stream().collect(Collectors.groupingBy(Trade::getTid, Collectors.counting()));
//還可以把groupingBy和mapping收集器結合起來,映射成一個list缺厉,甚至一個set
Map<String, List<Integer>> trade1 = trades.stream().collect(Collectors.groupingBy(Trade::getTid, Collectors.mapping(x -> {
if (x.getTid().equals("trade")) {
return 1;
}
return 0;
}, Collectors.toList())));
8.自定義收集器
只需實現(xiàn) Collectors 接口中的方法就可以自己實現(xiàn)一個收集器
public class CollectorExa<T> implements Collector<T, List<T>, List<T>> {
/**
* 在調(diào)用時它會創(chuàng)建一個空的累加器實例永高,供數(shù)據(jù)收集過程使用。
*/
@Override
public Supplier<List<T>> supplier() {
return ()->new ArrayList<>();
}
/**
* 將元素添加到結果容器
* 當遍歷到流中第n個元素時提针,這個函數(shù)執(zhí)行
* 時會有兩個參數(shù):保存歸約結果的累加器(已收集了流中的前 n?1 個項目)命爬,還有第n個元素本身。
* 該函數(shù)將返回void辐脖,因為累加器是原位更新饲宛,即函數(shù)的執(zhí)行改變了它的內(nèi)部狀態(tài)以體現(xiàn)遍歷的
* 元素的效果。
*/
@Override
public BiConsumer<List<T>, T> accumulator() {
return (list,item)->list.add(item);
}
/**
* combiner方法會返回一個供歸約操作使用的函數(shù)嗜价,它定義了對
* 流的各個子部分進行并行處理時艇抠,各個子部分歸約所得的累加器要如何合并。對于toList而言久锥,
* 這個方法的實現(xiàn)非常簡單家淤,只要把從流的第二個部分收集到的項目列表加到遍歷第一部分時得到
* 的列表后面就行了:
*/
@Override
public BinaryOperator<List<T>> combiner() {
return (list1, list2) -> {
list1.addAll(list2);
return list1; };
}
/**
* 在遍歷完流后,finisher方法必須返回在累積過程的最后要調(diào)用的一個函數(shù)瑟由,以便將累加
* 器對象轉(zhuǎn)換為整個集合操作的最終結果媒鼓。
*/
@Override
public Function<List<T>, List<T>> finisher() {
return Function.identity();
}
/**
* 最后一個方法——characteristics會返回一個不可變的Characteristics集合,它定義
* 了收集器的行為——尤其是關于流是否可以并行歸約,以及可以使用哪些優(yōu)化的提示绿鸣。
*
* Characteristics是一個包含三個項目的枚舉疚沐。
*
* UNORDERED——歸約結果不受流中項目的遍歷和累積順序的影響。
* CONCURRENT——accumulator函數(shù)可以從多個線程同時調(diào)用潮模,且該收集器可以并行歸
* 約流亮蛔。如果收集器沒有標為UNORDERED,那它僅在用于無序數(shù)據(jù)源時才可以并行歸約擎厢。
* IDENTITY_FINISH——這表明完成器方法返回的函數(shù)是一個恒等函數(shù)究流,可以跳過。這種
* 情況下动遭,累加器對象將會直接用作歸約過程的最終結果芬探。這也意味著,將累加器A不加檢
* 查地轉(zhuǎn)換為結果R是安全的厘惦。
*/
@Override
public Set<Characteristics> characteristics() {
return Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH));
}
}
// 嘗試使用
Stream.of(5,4,5,6,7).collect(new CollectorExa<>());
9.并行流
將流轉(zhuǎn)為并行流只需要中間調(diào)用一個 .parallel() 方法即可偷仿,調(diào)用之后流內(nèi)會設置一個標志,同樣的調(diào)用 sequential()可以把它編程順序流宵蕉,但是下面的操作需要注意
//此方法并不會先并行酝静,在順序執(zhí)行,在并行羡玛,由于流是惰性計算的别智,所以是否并行只會看最后一次調(diào)用,所以這個流是整體是并行的
//使用的線程池大小為運行機器上的內(nèi)核數(shù)稼稿,使用并行流不一定會比順序流更加高效薄榛,有可能會更慢,因為并行本身會消耗資源
Stream.parallel()
.filter(...)
.sequential()
.map(...)
.parallel()
.reduce();
10.Java Stream流的基石 monad 让歼、構造一個monad函數(shù)
monad范疇學上的解釋是 一個子函子上的幺半群
我理解的是把類型封裝起來敞恋,對原始類型的操作轉(zhuǎn)變成對封裝類型的操作 a+b ==> f(a)+f(b),推薦圖解 Monad - 阮一峰的網(wǎng)絡日志 (ruanyifeng.com)
//一個簡單的monad函數(shù)
public interface MonadExample<T> {
static <T> MonadExampleImp<T> of(T value){
return new MonadExampleImp<>(value);
}
<R> MonadExample<R> map(Function<T, R> function);
Optional<T> Has(T value);
class MonadExampleImp<T> implements MonadExample<T>{
private T value;
public MonadExampleImp(T value) {
this.value = value;
}
@Override
public <R> MonadExample<R> map(Function<T, R> function) {
return new MonadExampleImp<>(function.apply(value));
}
@Override
public Optional<T> Has(T value) {
if(value.equals(this.value)){
return Optional.of(this.value);
}
return Optional.empty();
}
public static void main(String[] args) {
Optional<String> has = MonadExample.of(4)
.map(x -> String.valueOf(4))
.map(x -> String.valueOf(4))
.map(x -> String.valueOf(4))
.Has("3");
System.out.println(has.orElse("-1"));
}
}
}
github有人寫了一個對try catch進行monad封裝的類庫 jasongoodwin/better-java-monads (github.com)
利用這個類庫可以把try catch延后處理是越,讓整個流的運算顯得更加合理
<!--maven引入依賴-->
<dependency>
<groupId>com.jason-goodwin</groupId>
<artifactId>better-monads</artifactId>
<version>0.4.0</version>
</dependency>
//如果流里面有異常需要拋出,只能按下面的的方式寫
List<Integer> old = Stream.of(5).map(x -> {
try {
Thread.sleep(4);
} catch (InterruptedException e) {
return -1;
}
return x;
}).collect(Collectors.toList());
//如果用類庫之后可以碌上,可以用一種更加合理的方式去處理異常
List<Integer> collect = Stream.of(5).map((x) -> Try.ofFailable(() -> {
Thread.sleep(400);
return x;
})).map(x -> x.orElse(-1)).collect(Collectors.toList());
------------ 本文內(nèi)容參考如下-------------
圖解 Monad - 阮一峰的網(wǎng)絡日志 (ruanyifeng.com)