天天看點

Java lambda&Stream1. lambda2. Stream

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 生成流

  1. 處理容器
  2. 處理數組

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

Java lambda&amp;Stream1. lambda2. Stream

debug的時候點選:

Java lambda&amp;Stream1. lambda2. Stream

2.5 知識彙總(※)

示意圖

Java lambda&amp;Stream1. lambda2. Stream

操作特性

  1. 不存儲資料
  2. 不改變資料源
  3. 不可重複使用(對一個流進行操作之後,要麼生成新的流繼續操作,要麼終值操作)

流的操作類型

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() 添加關閉監聽.