11、四大函數式接口 和 Stream流式計算(必須掌握) 和 ForkJoin任務拆分


引用學習(狂神說)

四大函數式接口

必須掌握的知識點

以前的程序員(知道jdk1.5的特性):

泛型枚舉反射和注解

新時代的程序員(因為jdk的版本都已經到13了):

所以要在這個三個基礎上,必須掌握4個:lambda表達式鏈式編程函數式接口Stream流式計算

函數式接口

介紹

  1. 只有一個方法的接口

  2. 接口上面有@FunctionalInterface(功能接口)的注解標注

    • 比如Runnable接口

    • @FunctionalInterface
      public interface Runnable {
          public abstract void run();
      }
  3. 簡化編程模型,在新版本的框架底層大量應用!

  4. foreach(消費者類的函數式接口)

  5. 四大函數式接口

Consumer函數式接口

  • 消費型接口:只有輸入,沒有輸出

使用代碼

 

package com.zxh.pool;

import java.util.function.Consumer;

// consumer:消費型接口:只有輸入,沒有輸出
public class FunctionInterfaceDemo {
    public static void main(String[] args) {
//        Consumer consumer = new Consumer<String>(){
//            @Override
//            public void accept(String str){
//                System.out.println(str);
//            }
//        };
//        consumer.accept("123");

        // 使用lambda表達式
        Consumer<String> consumer = (str)->{System.out.println(str);};
        consumer.accept("123");
    }
}

Supplier函數式接口

  • 提供型接口:沒有輸入,只有輸出

使用代碼

package com.zxh.pool;

import java.util.function.Supplier;

/**
 * 1、Consumer:消費型接口:只有輸入,沒有輸出
 * 2、Supplier:提供型接口:只有輸出,沒有輸入
 */
public class FunctionInterfaceDemo {
    public static void main(String[] args) {
//        Supplier<Integer> supplier = new Supplier<Integer>() {
//            @Override
//            public Integer get() {
//                return 1024;
//            }
//        };
        // 使用lambda表達式
        Supplier<Integer> supplier = ()->{return 1024;};
        System.out.println(supplier.get());
    }
}

Function函數式接口

  • 可以有輸入,可以有輸出

使用代碼

package com.zxh.function;

import java.util.function.Function;

/**
 * 1、Consumer:消費型接口:只有輸入,沒有輸出
 * 2、Supplier:提供型接口:只有輸出,沒有輸入
 * 3、Function:有輸入,也有輸出
 */
public class Demo03 {
    public static void main(String[] args) {
//        Function<String, String> function = new Function<String, String>() {
//            @Override
//            public String apply(String s) {
//                return s;
//            }
//        };

        // lambda表達式
        Function function = (str)->{return str;};
        System.out.println(function.apply("123"));
    }
}

Predicate函數式接口

  • 斷定型接口

  • 可以輸入,輸出boolean類型參數

使用測試 

package com.zxh.function;

import java.util.function.Predicate;

/**
 * 1、Consumer:消費型接口:只有輸入,沒有輸出
 * 2、Supplier:提供型接口:只有輸出,沒有輸入
 * 3、Function:有輸入,也有輸出
 * 4、Predicate:斷定型接口:有輸入,返回布爾型
 */
public class Demo04 {
    public static void main(String[] args) {
//        Predicate<String> predicate = new Predicate<String>() {
//            @Override
//            public boolean test(String str) {
//                return str.isEmpty();
//            }
//        };

        // 使用lambda表達式
        Predicate<String> predicate = (str)->{return str.isEmpty();};
        System.out.println(predicate.test("123"));
        System.out.println(predicate.test(""));
    }
}

Stream流式計算

什么是Stream流式計算

在大數據中:會有存儲 + 計算

集合(List)、MySQL本質就是存儲東西的!

而儲存的過程就交給流操作!

 

jdk1.8文檔中有Stream接口

例題:包含所有新特性

1、有一個實體類User

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private int id;
    private String name;
    private int age;
}

2、題目要求

/**
 * 題目要求:一分鍾內完成此題,只能用一行代碼實現!
 * 現在有5個用戶!篩選:
 * 1、ID 必須是偶數
 * 2、年齡必須大於23歲
 * 3、用戶名轉為大寫字母
 * 4、用戶名字母倒着排序
 * 5、只輸出一個用戶!
 */
public class Test {
    public static void main(String[] args) {
        User u1 = new User(1,"a",21);
        User u2 = new User(2,"b",22);
        User u3 = new User(3,"c",23);
        User u4 = new User(4,"d",24);
        User u5 = new User(6,"e",25);

        // 集合就是存儲
        List<User> users = Arrays.asList(u1, u2, u3, u4, u5);
    }
}

3、實現解析

Stream接口,涉及到的方法

  1. filter():參數Predicate,就是段定型的函數式接口

    • 該接口的參數可以進行判斷,並返回布爾型

    • 就可以做到過濾、篩選

  2. map():參數Function,就是只可以指定輸入輸出類型的函數式接口

    • 輸入小寫字母:轉為大寫字母並返回。

  3. sorted():參數Comparator,就是可以比較大小的函數式接口

    • 該接口的參數可以傳入兩個相同的類型的參數,進行比較,並且返回int類型的值。

    • 如果返回

    • Comparator函數式接口測試:

    • public class MyTest {
          public static void main(String[] args) {
              Comparator<Integer> comparator = (u1, u2)->{return u1 - u2;};
              /*
                  u1 - u2:u1和比u2是升序排列,
                      當 u1 - u2 < 0:也就是u1 < u2,不動
                      當 u1 - u2 > 0:也就是u1 > u2,換一下順序
                  u2 - u1:u2和比u1是降序排列,
                      當 u1 - u2 < 0:也就是u1 < u2,換一下順序
                      當 u1 - u2 > 0:也就是u1 > u2,不動
               */
              Arrays.asList(3, 1, 2).stream()
                      .sorted((u1, u2) -> {return u1 - u2;})
                      .forEach(System.out::println);
          }
      }
  4. limit():從上往下截取截取多少個參數

過濾:ID 必須是偶數

// stream流式計算,管理數據
// 應用的到了:lambda表達式、函數式接口、鏈式編程、Stream流式計算
users.stream()
    .filter((u)->{return u.getId()%2==0;}) // 過濾 ID 必須是偶數
    .forEach(System.out::println);

 

 過濾:年齡必須大於23歲

// stream流式計算,管理數據
// 應用的到了:lambda表達式、函數式接口、鏈式編程、Stream流式計算
users.stream()
    .filter((u)->{return u.getId()%2==0;}) // 過濾方法: ID 必須是偶數
    .filter((u)->{return u.getAge() > 23;}) // 過濾方法: 年齡必須大於23歲
    .forEach(System.out::println);

 

 轉換:用戶名轉為大寫字母

users.stream()
    // lambda表達式:如果參數只有一個可以省略括號
    .filter((u) -> {return u.getId()%2==0;}) // 過濾方法: ID 必須是偶數
    .filter(u ->{return u.getAge() > 23;}) // 過濾方法: 年齡必須大於23歲
    .map(u->{u.setName(u.getName().toUpperCase()); return u;})  // 轉換方法:用戶名轉為大寫字母
    .forEach(System.out::println);

 

 排序: 用戶名字母倒着排序

users.stream()
    // lambda表達式:如果參數只有一個可以省略括號
    .filter((u) -> {return u.getId()%2==0;}) // 過濾方法: ID 必須是偶數
    .filter(u ->{return u.getAge() > 23;}) // 過濾方法: 年齡必須大於23歲
    .map(u->{u.setName(u.getName().toUpperCase()); return u;})  // 轉換方法:用戶名轉為大寫字母
    .sorted((uu1, uu2)->{return uu2.getName().compareTo(uu1.getName());})  // 排序方法: 用戶名字母倒着排序
    .forEach(System.out::println);

 

 取指定個數:輸出一個用戶

users.stream()
    // lambda表達式:如果參數只有一個可以省略括號
    .filter((u) -> {return u.getId()%2==0;}) // 過濾方法: ID 必須是偶數
    .filter(u ->{return u.getAge() > 23;}) // 過濾方法: 年齡必須大於23歲
    .map(u->{u.setName(u.getName().toUpperCase()); return u;})  // 轉換方法:用戶名轉為大寫字母
    .sorted((uu1, uu2)->{return uu2.getName().compareTo(uu1.getName());})  // 排序方法: 用戶名字母倒着排序
    .limit(1)   // 取指定個數:輸出一個用戶
    .forEach(System.out::println);

完成代碼

package com.zxh.Stream;

import java.util.Arrays;
import java.util.List;

/**
 * 題目要求:一分鍾內完成此題,只能用一行代碼實現!
 * 現在有5個用戶!篩選:
 * 1、ID 必須是偶數
 * 2、年齡必須大於23歲
 * 3、用戶名轉為大寫字母
 * 4、用戶名字母倒着排序
 * 5、只輸出一個用戶!
 */
public class Test {
    public static void main(String[] args) {
        User u1 = new User(1,"a",21);
        User u2 = new User(2,"b",22);
        User u3 = new User(3,"c",23);
        User u4 = new User(4,"d",24);
        User u5 = new User(6,"e",25);

        // 集合就是存儲
        List<User> users = Arrays.asList(u1, u2, u3, u4, u5);

        // stream流式計算,管理數據
        // 應用的到了:lambda表達式、函數式接口、鏈式編程、Stream流式計算
        users.stream()
                // lambda表達式:如果參數只有一個可以省略括號
                .filter((u) -> {return u.getId()%2==0;}) // 過濾方法: ID 必須是偶數
                .filter(u ->{return u.getAge() > 23;}) // 過濾方法: 年齡必須大於23歲
                .map(u->{u.setName(u.getName().toUpperCase()); return u;})  // 轉換方法:用戶名轉為大寫字母
                .sorted((uu1, uu2)->{return uu2.getName().compareTo(uu1.getName());})  // 排序方法: 用戶名字母倒着排序
                .limit(1)   // 取指定個數:輸出一個用戶
                .forEach(System.out::println);
    }
}

 

ForkJoin任務拆分

ForkJoin是什么

ForkJoin在JDK1.7的時候就已經出來了,用於並行執行任務!提高效率

具體比如大數據的Map Reduce:將一個大任務拆分成多個小任務執行

概念圖

原本產生的結果是逐步往上回調給子任務,再進行拼接操作的,這里為了畫圖直接,產生最終結果。

ForkJoin特點

特點:工作竊取

 

這個里面維護的都是雙端隊列

概念圖

  • AB兩個線程,B線程執行完了會把A線程沒有執行完的任務偷過來了執行,增加效率

  • 但是會產生A和B線程爭奪這個任務的情況,但還是利大於弊的

ForkJoin如何使用

肯定需要知道ForkJoin是怎么創建運行的?

查看官方文檔

1、點開JUC工具包

 

2、可以發現關於ForkJoinPool的兩個接口

  • 它和線程池一樣,線程通過線程池創建,而ForkJoin通過ForkJoinPool創建並執行的。

  • 點開可以看到這兩個接口,對應只有一個相同的實現類ForkJoinPool

    •  

 

3、查看到實現類中有兩個關於ForkJoin的類

 

4、點開ForkJoinPool可以看到有說明

  1. 首先是實現的兩個接口

    • 隨便點開一個可以看到,線程池ThreadPoolExecutor和ForkJoinPool是同一級的。

  2. ForkJoinPool是運行ForkJoinTask的

 

5、怎么運行呢?ForkJoinPool對應提供了3個方法,具體如下:

  • ForkJoinPool有3個主要的執行方法

  1. 只執行任務,沒有返回值

  2. 執行任務,並返回結果

    •  

6、再查看ForkJoinTask這個類是什么?

  • 有兩個實現類

  •  

7、這里我們使用RecursiveTask有返回值的實現類

  • 怎么使用呢?文檔中有案例,直接繼承這個類就可以了

  • 查看源碼,可以發現需要重寫一個抽象方法,該方法就是計算的

  •  

我們了解如何創建並使用ForkJoin,那我們接下來舉個例子,從1到10億進行累加,使用3中方法累加,查看最后的運行效率

舉例測試

普通方法

package com.zxh.forkjoin;

public class Test {
    public static void main(String[] args) {
        test1();
    }
    
    // 普通方法
    public static void test1(){
        Long start = System.currentTimeMillis();

        Long sum = 0L;
//        使用JDK1.7的分隔符,可以講10億寫為:10_0000_0000
        for (Long i = 1L; i <= 10_0000_0000; i++) {
            sum += i;
        }

        Long end = System.currentTimeMillis();
        System.out.println("sum = "+ sum + " 運行時間:" + (end - start));
    }

}

 

 使用ForkJoin優化方法

package com.zxh.forkjoin;

import java.util.concurrent.RecursiveTask;

/**
 * 這里模擬求和運算從start開始到end一直累加
 * 如何使用ForkJoin?
 * 1、通過forkJoinPool 執行
 * 2、執行計算任務 forkjoinPool.execute(ForkJoinTask task)
 * 3、計算類要繼承 RecursiveTask,因為他有返回值
 */
public class ForkJoinDemo extends RecursiveTask<Long> {

    private Long start; // 初始值
    private Long end;   // 最終值

    // 臨界值,用於判斷這個任務的大小,如果超出這個數,就對半分割任務執行,提高效率
    private Long temp = 10000L;

    public ForkJoinDemo(Long start, Long end) {
        this.start = start;
        this.end = end;
    }

    // 計算方法,就相當於遞歸的操作
    @Override
    protected Long compute() {  // 具體任務的執行,並返回執行后的值
        if((end - start) < temp){   // 判斷要計算的數字個數是否小邊界值
            Long sum = 0L;
//        使用JDK1.7的分隔符,可以講10億寫為:10_0000_0000
            for (Long i = start; i <= end; i++) {
                sum += i;
            }
            return sum; // 返回計算結果
        }else{
            Long middle = (start + end) / 2; // 取中間數,將任務分割
            ForkJoinDemo task1 = new ForkJoinDemo(start, middle);
            task1.fork();   // 將拆分后的任務,壓入線程隊列
            ForkJoinDemo task2 = new ForkJoinDemo(middle + 1, end);
            task2.fork();   // 將拆分后的任務,壓入線程隊列
            return task1.join() + task2.join(); // 獲取結果,並返回計算結果
        }
    }
}

測試類

package com.zxh.forkjoin;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.LongStream;

public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
       test2();    //3993
    }

    // ForkJoin優化方法
    public static void test2() throws ExecutionException, InterruptedException {
        Long start = System.currentTimeMillis();

        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinDemo task = new ForkJoinDemo(0L, 10_0000_0000L);    // 計算任務
        ForkJoinTask<Long> submit = forkJoinPool.submit(task);  // 提交任務
        Long sum = submit.get();

        Long end = System.currentTimeMillis();
        System.out.println("sum = "+ sum + " 運行時間:" + (end - start));
    }

}

 

使用Stream並行流

package com.zxh.forkjoin;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.LongStream;

public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
       test3();    //212
    }

    // Stream流並行執行方法
    public static void test3(){
        Long start = System.currentTimeMillis();
        // LongStream,並行流
        // rangeClosed():(]最開右閉,設置計算數字從1到10_0000_0000
        // parallel():開啟並行計算
        // reduce():求和方式,並返回結果
        long sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);

        Long end = System.currentTimeMillis();
        System.out.println("sum = "+ sum + " 運行時間:" + (end - start));
    }
}

小結

有3 6 9的說法,說是工資3000 6000 9000,上面的方法對應着工資,如果想拿到高工資,使用Stream流是最好的了,至於底層還需要研究,掌握這么一點是不行的。


免責聲明!

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



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