java面試題:Stream和方法引用

Stream流:

前提:你需要先了解lambda表達式:
lambda表達式
說到Stream便容易想到I/O Stream,而實際上,誰規定“流”就一定是“IO流”呢?在Java 8中,得益於Lambda所帶來的函數式編程,引入了一個全新的Stream概念,用於解決已有集合類庫既有的弊端。

思想轉變:

整體來看,流式思想類似於工廠車間的“生產流水線”。當需要對多個元素進行操作(特別是多步操作)的時候,考慮到性能及便利性,我們應該首先拼好一個“模型”步驟方案,然後再按照方案去執行它。

Stream(流)是一個來自數據源的元素隊列

**元素是特定類型的對象,**形成一個隊列。Java中的Stream並不會存儲元素,而是按需計算。
數據源 流的來源。 可以是集合,數組 等。
和以前的Collection操作不同, Stream操作還有兩個基礎的特徵:
Pipelining: 中間操作都會返回流對象本身。 這樣多個操作可以串聯成一個管道, 如同流式風格(fluent
style)。 這樣做可以對操作進行優化, 比如延遲執行(laziness)和短路( short-circuiting)。
內部迭代: 以前對集合遍歷都是通過Iterator或者增強for的方式, 顯式的在集合外部進行迭代, 這叫做外部迭
代。 Stream提供了內部迭代的方式,流可以直接調用遍歷方法。

**只有一次:**流只能用一次,用完之後會銷燬,屬於管道流。

流使用步驟:

當使用一個流的時候,通常包括三個基本步驟:獲取一個數據源(source)→ 數據轉換→執行操作獲取想要的結
果,每次轉換原有 Stream 對象不改變,返回一個新的 Stream 對象(可以有多次轉換),這就允許對其操作可以
像鏈條一樣排列,變成一個管道。

傳統集合的多步遍歷代碼

幾乎所有的集合(如 Collection 接口或 Map 接口等)都支持直接或間接的遍歷操作。而當我們需要對集合中的元
素進行操作的時候,除了必需的添加、刪除、獲取外,最典型的就是集合遍歷。

 List<String> zhangList = new ArrayList<>();
        for (String name : list) {
            if (name.startsWith("張")) {
                zhangList.add(name);
            }
        }
 
        List<String> shortList = new ArrayList<>();
        for (String name : zhangList) {
            if (name.length() == 3) {
                shortList.add(name);
            }
        }
 
        for (String name : shortList) {
            System.out.println(name);
        }

使用流多步遍歷代碼:

list.stream()
            .filter(s ‐> s.startsWith("張"))
            .filter(s ‐> s.length() == 3)
            .forEach(System.out::println);

獲取流

java.util.stream.Stream 是Java 8新加入的最常用的流接口。(這並不是一個函數式接口。)
獲取一個流非常簡單,有以下幾種常用的方式:
所有的 Collection 集合都可以通過 stream 默認方法獲取流;
Stream 接口的靜態方法 of 可以獲取數組對應的流。

根據Collection獲取流

list.stream()

根據Map獲取流

java.util.Map 接口不是 Collection 的子接口,且其K-V數據結構不符合流元素的單一特徵,所以獲取對應的流
需要分key、value或entry等情況:

 Stream<String> keyStream = map.keySet().stream();
 Stream<String> valueStream = map.values().stream();
 Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream();

根據數組獲取流

stream.of(ary)

流的常用方法:

延遲方法:返回值類型仍然是 Stream 接口自身類型的方法,因此支持鏈式調用。(除了終結方法外,其餘方
法均爲延遲方法。)
終結方法:返回值類型不再是 Stream 接口自身類型的方法,因此不再支持類似 StringBuilder 那樣的鏈式調
用。本小節中,終結方法包括 count 和 forEach 方法。

逐一處理:forEach

作用:遍歷數據

import java.util.stream.Stream;
public class Demo12StreamForEach {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("張無忌", "張三丰", "周芷若");
        stream.forEach(name‐> System.out.println(name));
    }
}

這裏隱藏了一個Consumer接口

java.util.function.Consumer接口是一個消費型接口。
Consumer接口中包含抽象方法void accept(T t),意爲消費一個指定泛型的數據。

過濾:filter

作用:可以通過 filter 方法將一個流轉換成另一個子集流。

import java.util.stream.Stream;
 
public class Demo07StreamFilter {
    public static void main(String[] args) {
        Stream<String> original = Stream.of("張無忌", "張三丰", "周芷若");
        Stream<String> result = original.filter(s ‐> s.startsWith("張"));
    }
}

該接口接收一個 Predicate 函數式接口參數(可以是一個Lambda或方法引用)作爲篩選條件。

該方法將會產生一個boolean值結果,代表指定的條件是否滿足。如果結果爲true,那麼Stream流的 filter 方法
將會留用元素;如果結果爲false,那麼 filter 方法將會捨棄元素。

映射:map

作用:如果需要將流中的元素映射到另一個流中,可以使用 map 方法

import java.util.stream.Stream;
 
public class Demo08StreamMap {
    public static void main(String[] args) {
        Stream<String> original = Stream.of("10", "12", "18");
        Stream<Integer> result = original.map(str‐>Integer.parseInt(str));
    }
}

這裏隱藏了function接口

這可以將一種T類型轉換成爲R類型,而這種轉換的動作,就稱爲“映射”。

統計個數:count

正如舊集合 Collection 當中的 size 方法一樣,流提供 count 方法來數一數其中的元素個數:

import java.util.stream.Stream;
 
public class Demo09StreamCount {
    public static void main(String[] args) {
        Stream<String> original = Stream.of("張無忌", "張三丰", "周芷若");
        Stream<String> result = original.filter(s ‐> s.startsWith("張"));
        System.out.println(result.count()); // 2
    }
}

取用前幾個:limit

limit 方法可以對流進行截取,只取用前n個。

import java.util.stream.Stream;
 
public class Demo10StreamLimit {
    public static void main(String[] args) {
        Stream<String> original = Stream.of("張無忌", "張三丰", "周芷若");
        Stream<String> result = original.limit(2);
        System.out.println(result.count()); // 2
    }
}

跳過前幾個:skip

如果希望跳過前幾個元素,可以使用 skip 方法獲取一個截取之後的新流:

如果流的當前長度大於n,則跳過前n個;否則將會得到一個長度爲0的空流。

import java.util.stream.Stream;
 
public class Demo11StreamSkip {
    public static void main(String[] args) {
        Stream<String> original = Stream.of("張無忌", "張三丰", "周芷若");
        Stream<String> result = original.skip(2);
        System.out.println(result.count()); // 1
    }
}

組合:concat

如果有兩個流,希望合併成爲一個流,那麼可以使用 Stream 接口的靜態方法 concat :

import java.util.stream.Stream;
 
public class Demo12StreamConcat {
    public static void main(String[] args) {
        Stream<String> streamA = Stream.of("張無忌");
        Stream<String> streamB = Stream.of("張翠山");
        Stream<String> result = Stream.concat(streamA, streamB);
    }
}

方法引用:

思想轉變:

lamdba存在冗餘的情況:

定義一個接口

@FunctionalInterface
public interface Printable {
    void print(String str);
}

使用lamdba表達式進行打印數據:

public class Demo01PrintSimple {
    private static void printString(Printable data) {
        data.print("Hello, World!");
    }
 
    public static void main(String[] args) {
        printString(s ‐> System.out.println(s));
    }
}

方法引用進一步來簡化代碼:

system.out對象已經存在

println方法也已經存在

public class Demo02PrintRef {
    private static void printString(Printable data) {
        data.print("Hello, World!");
    }
 
    public static void main(String[] args) {
        printString(System.out::println);
    }
}

請注意其中的雙冒號 :: 寫法,這被稱爲“方法引用”,而雙冒號是一種新的語法。

方法引用符

雙冒號 :: 爲引用運算符,而它所在的表達式被稱爲方法引用。如果Lambda要表達的函數方案已經存在於某個方
法的實現中,那麼則可以通過雙冒號來引用該方法作爲Lambda的替代者。

方法引用使用的場景:

在lamdba表達式中,或者匿名內部類中存在已經有的對象和方法,注意是替換lamdba表達式

使用對象名引用成員變量:

使用類名引用靜態成員方法:

使用supper引用父類的成員方法:

supper::父類成員方法

使用this引用本類的成員方法

this::成員方法

類構造器引用

類名::new

數組的構造器引用

int[]::new
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章