lambda&Stream
- 1. lambda
-
- 1.1 函數式接口
- 1.2 表達式編寫方法
- 2. Stream
-
- 2.1 Stream介紹
- 2.2 流的三種操作
-
- 2.2.1 生成流
- 2.2.2 中間操作
- 2.2.3 終值操作
- 2.3 流的執行順序
- 2.4 IDEA可視化Stream
- 2.5 知識彙總(※)
1. lambda
Lambada 簡介:
Lambda 表達式,也可稱為閉包,它是推動 Java 8 釋出的最重要新特性。
Lambda 允許把函數作為一個方法的參數(函數作為參數傳遞進方法中)。使用 Lambda 表達式可以使代碼變的更加簡潔緊湊
記住:函數作為參數傳遞進方法中
兩個東西:函數參數、方法
示例:
匿名類寫法
new Thread(new Runnable(){
@Override
public void run(){
System.out.println("hello");
}
}).start();
Lambada寫法
new Thread(()->System.out.println("hello")).start();
上述例子:
方法:Thread().start();
函數參數:()->System.out.println(“hello”)
這個函數參數實際上是Runnable中的run函數
編譯器會将 “System.out.println(“hello”)” 編譯成Runnable.run 的執行指令。
可代碼中我們并沒有指明Run方法,這是因為 run 方法是Runnable接口的唯一方法,也就是說如果Runable有多個方法是不能使用Lambada表達示的,這種支援Lambada的接口統稱函數式接口。
函數參數的寫法:
() -> {}
():接口方法的括号,接口方法如果有參數,也需要寫參數。若隻有一個參數時,括号可以省略。
-> : 分割左右部分。
{} : 要實作的方法體。隻有一行代碼時,可以不加括号,可以不寫return。
1.1 函數式接口
必須是 函數式接口 才可以使用lambada 表達示 ,函數式接口籠統的講就是隻有一個抽像方法接口就是函數式接口,其詳細特征如下:
- 接口中隻有一個抽像方法 會被編譯器自動認識成函數式接口
- 有多個方法,但是Object類提供的方法和default方法除外
1.2 表達式編寫方法
expression/ɪkˈspreʃn/:單條語句表達式
statement:語句塊
reference:方法引用
package com.lrm.web;
public class Test {
public static void main(String[] args) {
//參數聲明
creat((a,b)->a+b);
//單行語句塊:必須要有結果
creat((a,b)->a+b);
//多行語句塊
creat((a,b)->{
System.out.println("1");
return a+b;
});
//靜态引用
creat(Test::staticMethod);//引用Test類下的static_me方法
//非靜态引用
creat(new Test()::notStatic);
//工具類方法引用
creat(String::concat);
}
private String notStatic(String name,String message){
return name+message;
}
private static String staticMethod(String name, String message){
return name+message;
}
private static void creat(myInter myInter){
myInter.build("11","22");
}
public interface myInter{
String build(String name, String message);
}
}
2. Stream
2.1 Stream介紹
java8 中的stream 與InputStream和OutputStream是完全不同的概念, stream 是用于對集合疊代器的增強,使之完成 能夠完成更高效的聚合操作(過濾、排序、統計分組)或者大批量資料操作。此外與stream 與lambda 表達示結合後編碼效率與大大提高,并且可讀性更強。
示例展示:
// 擷取所有紅色蘋果的總重量
appleStore.stream().filter(a -> "red".equals(a.getColor()))
.mapToInt(w -> w.getWeight()).sum()
// 基于顔色統計平均重量
appleStore.stream().collect(Collectors.groupingBy(a -> a.getColor(),
Collectors.averagingInt(a -> a.getWeight()))).forEach((k, v) -> {
System.out.println(k + ":" + v);
});
stream 産生背景
‘擷取所有紅色蘋果的總重量’,如果用SQL其實非常好實作,為什麼不在直接關系資料庫來實作呢?
//擷取所有紅色蘋果的總重量
select sum(a.weight) from apple as a where a.color='red';
// 基于顔色分組統計重量
select a.color,sum(a.weight) from apple as a group by color;
周遊在傳統的javaEE 項目中資料源比較單而且集中,像這類的需求都我們可能通過關系資料庫中進行擷取計算。但現在的網際網路項目資料源成多樣化有:關系資料庫、NoSQL、Redis、mongodb、ElasticSearch、Cloud Server 等。這時就需我們從各資料源中彙聚資料并進行統計。這在Stream出現之前隻能過周遊實作 非常繁瑣。
Stream可以解決多資料源的資料操作相關問題
場景:跨庫join的問題
查詢一個店鋪的訂單資訊,需要用到訂單表與會員表 在傳統資料庫單一例中 可以通過jon 關聯輕松實作,但在分布場景中 這兩張表分别存儲在于 交易庫 和會員庫 兩個執行個體中,join不能用。隻能在服務端實作其流程如下:
查詢訂單表資料
找出訂單中所有會員的ID
根據會員ID查詢會員表資訊
将訂單資料與會員資料進行合并
這用傳統疊代方法非常繁瑣,而這正是stream 所擅長的。示例代碼如下:
// 擷取所有會員ID 并去重
List<Integer> ids = orders.stream().map(o -> o.getMemberId()).distinct().collect(Collectors.toList());
// 合并會員資訊 至訂單資訊
orders.stream().forEach(o -> {
Member member = members.stream().filter(m -> m.getId() == o.getMemberId()).findAny().get();
o.setMemberName(member.getName());
});
2.2 流的三種操作
2.2.1 生成流
- 處理容器
- 處理數組
2.2.2 中間操作
非靜态、傳回Stream的方法,都是中間操作(惰性操作)
惰性操作:如果沒有終值操作,中間操作不會被執行
2.2.3 終值操作
非中間操作的都是終值操作,隻能有一個且為最後一個,Stream的結果為終值方法的傳回值
2.3 流的執行順序
public void test(){
appleStore.stream(){
.peek(a->System.out.println(a.getColor()))//列印顔色
.peek(a->System.out.println(a.getOrigin()))//列印産地
.toArray();
}
上述代碼相當于
public void test(){
for(Apple apple : appleStore){
System.out.println(a.getColor());//列印顔色
System.out.println(a.getOrigin());//列印産地
}
是以執行順序為:
以對象為基本機關,依次操作
2.4 IDEA可視化Stream
debug的時候點選:
2.5 知識彙總(※)
示意圖
操作特性
- 不存儲資料
- 不改變資料源
- 不可重複使用(對一個流進行操作之後,要麼生成新的流繼續操作,要麼終值操作)
流的操作類型
stream 所有操作組合在一起即變成了管道,管道中有以下兩種操作:
- 中間操作(intermediate /,ɪntə’miːdɪət/): 調用中間操作方法會傳回一個新的流。通過連續執行多個操作倒便就組成了Stream中的執行管道(pipeline)。需要注意的是這些管道被添加後并不會真正執行,隻有等到調用終值操作之後才會執行。
-
終值操作(terminal /'tɜːmɪn(ə)l/): 在調用該方法後,将執行之前所有的中間操作,獲傳回結果結束對流的使用
流的執行順序說明:其每個元素挨着作為參數去調用中間操作及終值操作,而不是周遊完一個方法,在周遊下一個方法。
流的并形操作
調用Stream.parallel() 方法可以将流基于多個線程并行執行
流的生成
Collection#stream
Arrays#stream
Stream#Stream
Stream#generate
Stream 中的常用API及場景
方法 | 描述 | 操作類型 |
---|---|---|
filter | 接收一個Boolean表達示來過濾元素 | 中間操作 |
map | 将流中元素 1:1 映謝成另外一個元素 | 中間操作 |
mapToInt | 将流中元素映謝成int,mapToLong、mapToDouble操作類似目的減少 裝箱拆箱帶來的損耗 | 中間操作 |
flatMap | 如map時傳回的是一個List, 将會進一步拆分。詳見flatMap示例 | 中間操作 |
forEach | 周遊流中所有元素 | 終值操作 |
sorted | 排序 | 中間操作 |
peek | 周遊流中所有元素 ,如forEach不同在于不會結束流 | 中間操作 |
toArray | 将流中元素轉換成一個數組傳回 | 終值操作 |
reduce | 歸約合并操作 | 中間操作 |
collect | 采集資料,傳回一個新的結果 參數說明 Supplier<R>: 采集需要傳回的結果BiConsumer<R, ? super T>:傳遞結果與元素進行合并。BiConsumer<R, R>:在并發執行的時候 結果合并操作。詳見 collec示例 | 終值操作 |
distinct | 基于equal 表達示去重 | 中間操作 |
max | 通過比較函數 傳回最大值 | 終值操作 |
anyMatch | 流中是否有任一進制素滿足表達示 | 終值操作 |
allMatch | 流中所有元素滿足表達示傳回true | 終值操作 |
noneMatch | 與allMatch 相反,都不滿足的情況下傳回 true | 終值操作 |
findFirst | 找出流中第一個元素 | 終值操作 |
of | 生成流 | 生成流操作 |
iterate | 基于疊代生成流 | 生成流操作 |
generate | 基于疊代生成流,與iterate 不同的是不 後一進制素的生成,不依懶前一進制素 | 生成流操作 |
concat | 合并兩個相同類型的類 | 生成流操作 |
示例
@Test
public void filterTest() {
appleStore.stream().filter(a -> a.getColor().equals("red")).forEach(a -> {
System.out.println(a.getColor());
});
}
@Test
public void mapTest() {
appleStore.stream().map(a -> a.getOrigin()).forEach(System.out::println);
}
@Test
public void flatMapTest() throws IOException {
Stream<String> lines = Files.lines(new File("G:\\git\\tuling-java8\\src\\main\\java\\com\\tuling\\java8\\stream\\bean\\Order.java").toPath());
lines.flatMap(a -> Arrays.stream(a.split(" "))).forEach(System.out::println);
}
@Test
public void sortedTest() {
appleStore.stream().sorted((a, b) -> a.getWeight() - b.getWeight())
.map(a -> a.getWeight()).forEach(System.out::println);
}
@Test
public void peekTest() {
appleStore.stream().peek(a -> {
System.out.println(a.getId());
}).map(a -> a.getOrigin())
.peek(System.out::println).forEach(a -> {
});
}
@Test
public void reduceTest() {
// 找出最重的那個蘋果
appleStore.stream().reduce((a, b) -> a.getWeight() > b.getWeight() ? a : b)
.ifPresent(a -> {
System.out.println(a.getWeight());
});
}
@Test
public void collectTest() {
// 将結果轉換成id作為key map<Integer,Apple>
HashMap<Integer, Apple> map = appleStore.stream().collect(HashMap::new, (m, a) -> m.put(a.getId(), a), (m1, m2) -> m1.putAll(m2));
map.forEach((k, v) -> {
System.out.println(k);
System.out.println(v);
});
// Map<String,List<Apple>>
// 基于顔色分組, 并擷取其平均重量
}
Collectors 中的常用API及場景
方法 | 描述 |
---|---|
toList | 轉換成list |
toMap | 轉換成map |
groupingBy | 統計分組 |
averagingInt | 求平均值 |
summingInt | 求總值 |
maxBy | 擷取最大值 |
Collectors 使用例子
// 獲得所有顔色蘋果的平均重量
@Test
public void groupByTest() {
Collector<Apple, ?, Map<String, Double>> groupCollect =
Collectors.groupingBy((Apple a) -> a.getColor(), Collectors.averagingInt((Apple a) -> a.getWeight()));
appleStore.stream().collect(groupCollect).forEach((k, v) -> {
System.out.println(k + ":" + v);
});
}
流的關閉機制
一般情況使用完流之後不需要調用close 方法進行關閉,除非是使用channel FileInputStream 這類的操作需要關閉,可調用 java.util.stream.BaseStream#onClose() 添加關閉監聽.