常用函數式接口與Stream API簡單講解


常用函數式接口與Stream API簡單講解

Stream簡直不要太好使啊!!!

常用函數式接口

  • Supplier<T>,主要方法:T get(),這是一個生產者,可以提供一個T對象。
  • Consumer<T>,主要方法:void accept(T),這是一個消費者,默認方法:andthen(),稍后執行。
  • Predicate<T>,主要方法:boolean test(T t),這是一個判斷者,默認方法:and():且,or():或,negate():非。
  • Function<T,R>,主要方法:R apply(T t),這是一個修改者,默認方法:compose():優先執行,andThen(),稍后執行,identity():直接傳自身。
Function f1 = e -> e + 1;
Function f2 = e -> e * 1;
f1.andthen(f2).apply(t)//即f2.apply(f1.apply(t))
f1.compose(f2).apply(t)//即f1.apply(f2.apply(t))
而identify()相當於 s -> s

常用Stream方法:

中間操作是惰性求值,中間操作過后返回的還是Stream,因此可以繼續操作;結束操作是立即求值,返回具體的數據。

流不能被重用,因此推薦鏈式寫法。

上邊僅僅是Stream的方法,經常使用的還有Stream的靜態方法,比如:Stream.concat(),用來合成兩個流;Stream.of()方法,直接獲取流。

  • 使用流處理int數組時,可以使用Arrars.stream(arr)或者IntStream.of(arr)進行初始的流轉化,而Stream.of(arr)會把arr當做數組對象傳入,而非直接操作數組。(Arrays.stream 是為了彌補Stream.of無法使用數組參數而做的補償方案.比如a[]傳入Stream.of會被當成一個對象而傳入Arrays.stream可以當成一個數組)

具體區別可以由下面幾個栗子看出來:

int[] arr = {1,2,3,4};
System.out.println(
        Arrays.stream(arr).count());//4,因為數組里有4個數
System.out.println(
        Stream.of(arr).count());//1,因為只有一個數組,並且這里的流不能調用sum()方法求和,因為引用對象沒法相加
Stream.of(1,2,3,4).count();//4,因為有四個Integer,注意這里Stream.of()方法會自動裝箱,把int變為Integer,這里也不能調用sum()方法
Arrays.stream(new int[]{1,2,3,4}).count();//4,等價於第一種情況,並且此時還有個box()方法可以裝箱,因此使用流處理拆裝箱非常方便
//但是!如果Stream.of()參數是一個引用類型的數組(注意是一個),比如String[]:
System.out.println(
        Stream.of(new String[]{"My", "Java", "My", "Life!"}).count());//4
//它又可以正常使用,
//但是!!如果里面是兩個String[]數組,它又不好使了:
String[] s1 = {"My", "Java", "My", "Life!"};
String[] s2 = {""};
System.out.println(
        Stream.of(s1,s2).count());//2!!!

小結:說得好,數組我選擇Arrays.Stream()! (`へ´*)ノ

主要方法介紹:

forEach
forEach():void forEach(Consumer<? super E> action),
即對每個元素執行action,也就是最常見的遍歷

Stream.of("My","Java","My","Life!")
        .forEach(System.out::println);//打印每個元素

filter
filter():Stream<T> filter(Predicate<? super T> predicate),篩選,即挑選出符合predicate的元素

Stream.of("My", "Java", "My", "Life!")
        .filter(s -> s.length() > 4);//篩選出長度大於4的元素,此時流里的元素為:"Java","Life!"

distinct
distinct():Stream<T> distinct(),去重

Stream.of("My", "Java", "My", "Life!")
        .distinct();//"My","Java","Life!"

sorted
sorted():Stream<T> sorted()Stream<T> sorted(Comparator<? super T> comparator),排序,無參為自然排序,有參為自定義比較器排序

Stream.of("My", "Java", "My", "Life!")
        .sorted()//"Java","Life!","My","My"無參,也就是按照默認排序(這里就是ASCII碼)
        .forEach(System.out::println);
Stream.of("My", "Java", "My", "Life!")
        .sorted((s1, s2) -> s2.length() - s1.length())//"Life!","Java","My","My",按照長度進行降序排序
        .forEach(System.out::print);

map
map():<R> Stream<R> map(Function<? super T,? extends R> mapper),轉換,也就是執行Function的功能

class Student{
    private String name;

    public Student(String name){
        this.name = name;
    }
}

Stream.of("My", "Java", "My", "Life!")
        .map(String::toLowerCase)//轉化為小寫
        .map(Student::new);//接着以名字為參數轉化Student流

flatMap
flatMap():<R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper),扁平化轉換流,與map()不同的是,map()返回一個結果,而當返回結果數目未知時不太方便,使用flatMap()會將多個數目不一的流合為一個流

Stream.of(Arrays.asList("My"), Arrays.asList("Java", "My", "Life!"))//此時,這是List流,里面是兩個List對象,List里面才是字符串數據
        .flatMap(list -> list.stream())//扁平化,即把List流轉化為字符串流,這里也可以使用方法引用:.flatMap(Collection::stream)
        .forEach(System.out::println);

count
count():long count(),統計流中元素的數量,注意返回值為long

Stream.of("My", "Java", "My", "Life!")
        .count();//4L

limit
limit():Stream<T> limit(long maxSize),限制長度,也可以理解為取前maxSize個元素,maxSize可以大於元素數目

Stream.of("My", "Java", "My", "Life!")
        .limit(2);//"My","Java"

skip
skip():Stream<T> skip(long n),跳過前n個元素,和limit差不多

Stream.of("My", "Java", "My", "Life!")
        .skip(2);//"My","Life!"

toArray
toArray:Object[] toArray()<A> A[] toArray(IntFunction<A[]> generator),無參即返回Object[]數組,而有參時可以返回特定類型數組

Stream.of("My", "Java", "My", "Life!")
        .toArray(String[]::new);//String[]數組,內容為:["My", "Java", "My", "Life!"]

parallel
parallel:S parallel()轉化為並行流,但是單核甚至雙核的情況下轉化並行流反而會降低效率。

max
max():Optional<T> max(Comparator<? super T> comparator),以自定義比較方法返回一個最大對象,注意這里返回的是Optional對象。

Stream.of("My", "Java", "My", "Life!")
        .max((o1, o2) -> o2.length()-o1.length())
        .get();//"My"

Stream.of("My", "Java", "My", "Life!")
        .max(Comparator.comparingInt(String::length))//"Life!",這里返回的是Optional,里面盛的是"Life!"
        .stream()//這里調用的是Optional的方法,其實這里沒有必要,直接調用get()方法就可以拿到"Life!"字符串了
        .forEach(System.out::println);

說到了max()就不得不提一下Optional是什么,簡單來說Optional就是一個容器,用來盛放數據,並且它是容許為null的,這樣就不用反復判斷值是否為空了。可以看一下Optional的方法:

public final class Optional<T> {
    //靜態方法,返回空Optional對象
    public static<T> Optional<T> empty() {
    }

    //靜態方法,返回Optional對象,value不能為空
    public static <T> Optional<T> of(T value) {
        return new Optional<>(value);
    }

    //靜態方法,返回Optional對象,值允許為null
    public static <T> Optional<T> ofNullable(T value) {
        return value == null ? empty() : of(value);
    }

    //雖然值允許為null,但還是在調用get時還是會拋出NoSuchElementException異常
    public T get() {
    }

    //所以此方法可以判斷值是否存在
    public boolean isPresent() {
    }

    //如果存在,則執行action
    public void ifPresent(Consumer<? super T> action) {
    }

    //JDK9方法↓
    //如果值非空,執行action,值為null則執行emptyAction
    public void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction) {
    }

    //Optional對象也可以執行與Stream相同的部分方法
    public Optional<T> filter(Predicate<? super T> predicate) {
    }
    public <U> Optional<U> map(Function<? super T, ? extends U> mapper) {
    }
    public <U> Optional<U> flatMap(Function<? super T, ? extends Optional<? extends U>> mapper) {
    }

    //JDK9方法↓
    //如果為null,則返回一個supplier提供的對象,與orElse和orElseGet方法類似。
    public Optional<T> or(Supplier<? extends Optional<? extends T>> supplier) {
    }

    //JDK9方法↓
    //可以把Optional對象繼續變成流
    public Stream<T> stream() {
    }

        public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
        if (value != null) {
            return value;
        } else {
            throw exceptionSupplier.get();
        }
    }

    //重寫equals方法
    @Override
    public boolean equals(Object obj) {
    }

    //重寫hashCode方法
    @Override
    public int hashCode() {
    }

    //重寫toString方法
    @Override
    public String toString() {
        return value != null
            ? String.format("Optional[%s]", value)
            : "Optional.empty";
    }
}

orElse和orElseGet:
orElseorElseGet這兩個方法放上源碼比較好理解:
首先,在值為null時,兩個方法並無區別,都能獲取到T;
但是,在非空時,如果orElse()的參數是一個方法,該方法也會被執行,而orElseGet()的參數supplier並不會執行,這得益於Lambda的延遲執行。因此,相同情況下orElseGet()性能更優。或者直接使用or,如果JDK允許的話

    public T orElse(T other) {
        return value != null ? value : other;
    }
    public T orElseGet(Supplier<? extends T> supplier) {
        return value != null ? value : supplier.get();
    }

舉個小栗子就能明白了:

static String B() {
        System.out.println("B()執行了");
        return "B";
}

public static void main(final String... args) {
    System.out.println(Optional.ofNullable(null).orElse(B()));
    System.out.println(Optional.ofNullable(null).orElseGet(() -> B()));
}

上面這種情況執行結果為:

B()執行了
B
B()執行了
B

並沒有什么問題,但是當值非空的時候:

static String B() {
    System.out.println("B()執行了");
    return "B";
}

public static void main(final String... args) {
    System.out.println(Optional.ofNullable("A").orElse(B()));
    System.out.println(Optional.ofNullable("A").orElseGet(() -> B()));
}

執行結果為:

B()執行了
A
A

很明顯orElseGet更節約性能,Lambda的延遲執行特性還是比較好用的。


咳咳,扯遠了,繼續說Stream的常用方法:

min
min():Optional<T> min(Comparator<? super T> comparator),與max類似,注意max()min()最好使用引用方法,而不是自己定義Comparator,舉個栗子:

Stream.of("My", "Java", "My", "Life!")
        .min((o1, o2) -> o2.length()-o1.length());//"Life!"

這里使用的是min()方法,自定義Comparator為常見的降序,但是最后得到的反而是長度最長的,完全違背了函數式見文知意的思想。

所以想要比較長度的話這樣寫:

Stream.of("My", "Java", "My", "Life!")
        .max(Comparator.comparingInt(String::length));
Stream.of("My", "Java", "My", "Life!")
        .min(Comparator.comparingInt(String::length));

或者,你覺得max(),min(),count(),sum()這些不好用,還有下面兩個多能工供你使用

reduce
reduce():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);

一個reduce操作(也稱為折疊)接受一系列的輸入元素,並通過重復應用操作將它們組合成一個簡單的結果,也就是持續迭代
理解reduce的含義重點就在於理解"累 加 器" 的概念,並且每次運算的結果會作為下一次運算的一個參數
Stream的一個參數和兩個參數的方法的基本邏輯都是如此,差別僅僅在於一個參數的是result R = T1 ,然后再繼續與剩下的元素參與運算

分別為一個參數、兩個參數、三個參數,這里需要先介紹一下這幾個新接口:

  • BiFunction
    • public interface BiFunction<T, U, R>
    • R apply(T t, U u)
    • 默認方法andthen
    • 它與Function不同點在於它接收兩個輸入返回一個輸出; 而Function接收一個輸入返回一個輸出。注意它的兩個輸入、一個輸出的類型可以是不一樣的。
  • BinaryOperator
    • public interface BinaryOperator<T> extends BiFunction<T,T,T>
    • 有兩個靜態方法:minBymaxBy
    • BiFunction的三個參數可以是一樣的也可以不一樣;而BinaryOperator就直接限定了其三個參數必須是一樣的;因此BinaryOperator與BiFunction的區別就在這。它表示的就是兩個相同類型的輸入經過計算后產生一個同類型的輸出。
  • BiConsumer
    • void accept(T t, U u)
    • Consumer變種,接收兩個參數,不返回

reduce一個接口參數可以很方便的進行求最大值、最小值、和,代碼如下:

Integer sum = s.reduce((a, b) -> a + b).get();
Integer max = s.reduce((a, b) -> a >= b ? a : b).get();
Integer min = s.reduce((a, b) -> a <= b ? a : b).get();

reduce兩個參數的對比一個參數的多了一個初始化的值,

T result = identity;
for (T element : this stream){
    result = accumulator.apply(result, element)
}
return result;

所以reduce(0, (a, b) -> a + b)reduce((a, b) -> a + b)是完全等價的。

reduice三個參數較為復雜,前兩個參數與二參基本相同,第三個參數用於在並行計算下,合並各個線程的計算結果。需要注意的是,並行情況下初始值(也就是第一個參數)會被多次計算,具體可以參看此博客

collect

collect
collect():<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)
收集裝置,可以將元素以特定方式收集起來。需要了解的是收集器的參數決定了收集器的行為
這里需要說一下Collector接口,這里先上幾個重要的靜態方法:

生成Collection

  • toCollection()將流轉化為集合,參數為集合實現方式,比如:
Stream.of("My", "Java", "My", "Life!")
        .collect(Collectors.toCollection(ArrayList::new));//以ArrayList的形式轉化為集合
  • toList()toset()這兩個可以視為上面的簡化版本,無參,默認為ArrayList和HashSet實現:
Stream.of("My", "Java", "My", "Life!")
        .collect(Collectors.toList());

生成Map

  • toMap與上面類似,只不過需要指定map的鍵和值:
Stream.of("My", "Java", "my", "Life!")
        .collect(Collectors.toMap(Function.identity(),//指定Key
                s -> s.length()))//指定Value
//注意,這里鍵是不能重復的,否則會拋出異常,所以我把第二個"My"改成了"my",想要鍵可重復請繼續往下看。
//運行結果
{Java=4, Life!=5, My=2, my=2}
  • partitioningBy()適用於將Stream中的元素依據某個二值邏輯(滿足條件,或不滿足)分成互補相交的兩部分,一參為一個Predicate接口,二參時可以將第二個參數指定為再一個Collector。
Map<Boolean, List<String>>  map = Stream.of("My", "Java", "my", "Life!")
        .collect(Collectors.partitioningBy(s -> s.length() > 3));//以長度是否大於3分組
//運行結果
{false=[My, my], true=[Java, Life!]}

//二參可以指定一個下游收集器
Map<Boolean, Long> map = Stream.of("My", "Java", "my", "Life!")
        .collect(Collectors.partitioningBy(s -> s.length() > 4, Collectors.counting()));//以長度是否大於4分組,並計數
//運行結果
{false=3, true=1}
  • groupingBy():上面的partitioningBy()只能將數據分成兩類,根本不夠用是不是,groupingBy()則可以做到更多。它分別有一參、二參和三參:
//一參可以當做partitioningBy用
Map<Boolean, List<String>> map = Stream.of("My", "Java", "My", "Life!")
        .collect(Collectors.groupingBy(s -> s.length() > 4));//以長度是否大於4分組
//運行結果
{false=[My, Java, My], true=[Life!]}
//也可以當做增強型
Map<Integer, List<String>> map = Stream.of("My", "Java", "My", "Life!")
        .collect(Collectors.groupingBy(String::length));//以長度分組
//運行結果
{2=[My, My], 4=[Java], 5=[Life!]}

//二參就更好用了
Map<String, Long> map = Stream.of("My", "Java", "My", "Life!")
        .collect(Collectors.groupingBy(
                Function.identity(),Collectors.counting()));//指定鍵為元素,值為出現次數,這種情況下鍵是可以重復的
//運行結果
{Java=1, Life!=1, My=2}
//或者
Map<Integer, Long> map = Stream.of("My", "Java", "My", "Life!")
        .collect(Collectors.groupingBy(String::length,Collectors.counting()));//以長度為鍵,出現次數為值
//運行結果
{2=2, 4=1, 5=1}

//三參就更厲害了
Map<Integer, List<String>> map = Stream.of("My", "Java", "My", "Life!")
        .collect(Collectors.groupingBy(
                String::length,Collectors.mapping(s->s.toUpperCase(),Collectors.toList())));//注意這里下游收集齊還包含着更下游的收集齊
//運行結果
{2=[MY, MY], 4=[JAVA], 5=[LIFE!]}

你可能覺得三參的collect用不到,但是請看這里:

下游收集器還可以包含更下游的收集器,這絕不是為了炫技而增加的把戲,而是實際場景需要。考慮將員工按照部門分組的場景,如果我們想得到每個員工的名字(字符串),而不是一個個Employee對象,可通過如下方式做到:

// 按照部門對員工分布組,並只保留員工的名字
Map<Department, List<String>> byDept = employees.stream()
               .collect(Collectors.groupingBy(Employee::getDepartment,
                       Collectors.mapping(Employee::getName,// 下游收集器
                               Collectors.toList())));// 更下游的收集器

超強!參見這篇博客

  • joining()這是用來連接字符串的,無參為直接連接,一參指定連接符,三參可以指定連接符和首位字符:
Stream.of("My", "Java", "My", "Life!")
        .collect(Collectors.joining());//MyJavaMyLife!
Stream.of("My", "Java", "My", "Life!")
        .collect(Collectors.joining("-")//My-Java-My-Life!
Stream.of("My", "Java", "My", "Life!")
        .collect(Collectors.joining("-","[","]"))//[My-Java-My-Life!]

小結:用了Stream之后再也不想用普通的for循環了有木有啊!!!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM