java8新特性lambda和Stream新手springboot案例2020年新版


一、前言

本文,前段是原理,后半段是案例,如果懶得看原理的朋友,可以直接跳到案例

敲黑板,跟我邊做邊學,直接到案例那一段,非常詳細。


 

什么是java8---關鍵字:2014年3月發布,提高與舊代碼的兼容性

目前已經到了java14了,JDK8是Oracle在2014年3月19日發布正式版的,最大的改進是Lambda表達式(以及因之帶來的函數式接口,很多原有類都做了變更,但能夠與以往版本兼容,堪稱奇功!),還有Stream API流式處理,joda-time等等一些新特性。
  • 1.default關鍵字
  • 2.Lambda 表達式(函數式編程)
  • 3.函數式接口
  • 4.方法與構造函數引用
  • 5.局部變量限制
  • 6.Date Api更新
  • 7.流(聲明性方式

 

什么是lambda---關鍵字:“語法糖”, 

  • 用逗號分隔的參數列表
  • -> 符號
  • 和 語句塊 組成
 
雖然看着很先進,其實Lambda表達式的本質只是一個"語法糖",由編譯器推斷並幫你轉換包裝為常規的代碼,因此你可以使用更少的代碼來實現同樣的功能。
 
Lambda表達式是Java SE 8中一個重要的新特性。lambda表達式允許你通過表達式來代替功能接口。 lambda表達式就和方法一樣,它提供了一個正常的參數列表和一個使用這些參數的主體(body,可以是一個表達式或一個代碼塊)。
 
在 Java8 以前,我們想要讓一個方法可以與用戶進行交互,比如說使用方法內的局部變量;
1.這時候就只能使用接口做為參數,讓用戶實現這個接口或使用匿名內部類的形式,把局部變量通過接口方法傳給用戶。
傳統匿名內部類缺點:代碼臃腫,難以閱讀
 
2.使用Lambda 表達式
Lambda 表達式將函數當成參數傳遞給某個方法,或者把代碼本身當作數據處理;
 
// 使用匿名內部類  
btn.setOnAction(new EventHandler<ActionEvent>() {  
          @Override  
          public void handle(ActionEvent event) {  
              System.out.println("Hello World!");   
          }  
    });  
   
// 或者使用 lambda expression  
btn.setOnAction(event -> System.out.println("Hello World!"));
 
Lambda表達式還增強了集合庫。 Java SE 8添加了2個對集合數據進行批量操作的包: java.util.function 包以及java.util.stream 包。

 

什么是函數式接口---關鍵字:默認方法允許在不打破現有繼承體系的基礎上改進接口

  • 接口中只能有一個接口方法
  • 可以有靜態方法和默認方法
  • 使用 @FunctionalInterface 標記
  • 默認方法可以被覆寫
 
由於JVM上的默認方法的實現在字節碼層面提供了支持,因此效率非常高。默認方法允許在不打破現有繼承體系的基礎上改進接口。該特性在官方庫中的應用是:給 java.util.Collection接口添加新方法,如 stream()、parallelStream()、forEach()和removeIf() 等等。
 
下圖,查看容器map的foreach的方法:
 
 
已經存在的 Java8 定義的函數式接口

我們基本不需要定義自己的函數式接口,Java8 已經給我們提供了大量的默認函數式接口,基本夠用,在 rt.jar 包的 java.util.function 目錄下可以看到所有默認的函數式接口,大致分為幾類
Function<T,R> T 作為輸入,返回的 R 作為輸出
Predicate<T> T 作為輸入 ,返回 boolean 值的輸出
Consumer<T> T 作為輸入 ,沒有輸出
Supplier<R> 沒有輸入 , R 作為輸出
BinaryOperator<T> 兩個 T 作為輸入 ,T 同樣是輸出
UnaryOperator<T> 是 Function 的變種 ,輸入輸出者是 T

 

什么是stream---關鍵字:集合,高級版本的iterator

java8中的集合支持一個新的Stream方法,它會返回一個流,到底什么是流呢?
 
流的使用包括三件事:
1.數據源,集合
2.中間操作,流水線
3.終端操作,執行流水線,生成結果
 
經典案例:
 
        //題目,排序,刪選大於6的
        //初始化
        List<Integer> integers = new ArrayList<>();
        integers.add(5);
        integers.add(7);
        integers.add(3);
        integers.add(8);
        integers.add(4);
        //傳統 1先排序(倒敘),2比較大小
        Collections.sort(integers, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2.compareTo(o1);
            }
        });
        Iterator<Integer> iterator = integers.iterator();
        while (iterator.hasNext()){
            Integer next = iterator.next();
            if (next > 6){
                iterator.remove();
            }
        }
        integers.forEach(System.out::println);

        //使用Stream
        List<Integer> collect = integers.stream().filter(i -> i < 6).sorted(Comparator.reverseOrder()).collect(Collectors.toList());
        collect.forEach(System.out::println);
 
Stream 不是集合元素,它不是數據結構並不保存數據,它是有關算法和計算的,它更像一個高級版本的 Iterator。
原始版本的 Iterator,用戶只能顯式地一個一個遍歷元素並對其執行某些操作;
高級版本的 Stream,用戶只要給出需要對其包含的元素執行什么操作,比如:
所有元素求和
過濾掉長度大於 10 的字符串
獲取每個字符串的首字母
Stream 就如同一個迭代器(Iterator),單向,不可往復,數據只能遍歷一次,遍歷過一次后即用盡了,就好比流水從面前流過,一去不復返。
而和迭代器又不同的是,Stream 可以並行化操作

Stream 的另外一大特點是,數據源本身可以是無限的

二、lambda案例

 1、從打印學lambds表達

我是用springboot做案例的,版本是 2.2.5.RELEASE

@SpringBootTest
class LambdsStreamApplicationTests {
    
    @Test
    public void printTest(){
        //打印list
        List<String> list = Arrays.asList("帥哥","燦燦");
        list.forEach(System.out::println);
        //打印map
        Map<Integer,String> map = new HashMap<>(5);
        map.put(1,"帥哥");
        map.put(2,"燦燦");
        map.forEach((k,v)->System.out.println("key: "+k+" value: "+v));
    }
    
}

輸出:

  • 帥哥
  • 燦燦
  • key: 1 value: 帥哥
  • key: 2 value: 燦燦

用了上面的打印后,循環遍歷打印感覺low出天際了

 

2、從打印學lambds表達2

    @Test
    public void printTest2(){
        //打印list
        List<String> list = Arrays.asList("帥哥","燦燦");
        list.forEach(v->System.out.println("至理名言:"+v));
        //打印map
        Map<Integer,String> map = new HashMap<>(5);
        map.put(1,"帥哥");
        map.put(2,"燦燦");
        map.forEach((k,v)->{
            if(k>1){
                System.out.println("key大於1,輸出至理名言, 我是"+v);
            }
        });
    }

輸出:

  • 至理名言:帥哥
  • 至理名言:燦燦
  • key大於1,輸出至理名言,我是燦燦

為什么我要舉兩個打印的例子,因為真的很重要,要消化一下。

 

3、從匿名內部類的對比來學lambds

 采用對比,來學匿名內部類和lambds的轉換,其實我看來,lambds就是用來解決匿名內部類的復雜問題

    @Test
    public void lambdsTest(){

        //之前,線程,匿名內部類,java8 之前
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        }).start();

        //之后,lambds表達式
        new Thread(()->System.out.println(Thread.currentThread().getName())).start();

        //==============================================================================
        
        //之前,線程,匿名內部類,java8 之前
        Runnable race1 = new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello ccan !");
            }
        };
        //之后,lambds表達式
        Runnable race2 = () -> System.out.println("Hello ccan !");
        race1.run();
        race2.run();
    }

 

 

4、函數式接口的案例,非常重要

由於JVM上的默認方法的實現在字節碼層面提供了支持,因此效率非常高。默認方法允許在不打破現有繼承體系的基礎上改進接口。該特性在官方庫中的應用是:給 java.util.Collection接口添加新方法,如 stream()、parallelStream()、forEach()和removeIf() 等等。
    @Test
    public void funcTest(){
        
        //函數式接口,這個非常重要,接口中只能有一個接口方法,可以有靜態方法和默認方法,使用 @FunctionalInterface 標記,默認方法可以被覆寫
        //函數式接口存在的意義非常重要, 默認方法允許在不打破現有繼承體系的基礎上改進接口
        //給 java.util.Collection接口添加新方法,如 stream()、parallelStream()、forEach()和removeIf() 等等
        Function<String,String> function = (x) -> {return x+"Function";};
        // hello world Function
        System.out.println(function.apply("hello world"));

        //==================================
        
        //使用函數式接口的map
        Map<Integer,String> map = new HashMap<>(5);
        map.put(1,"帥哥");
        map.put(2,"燦燦");
        //以下BiConsumer為函數式接口,源碼貼有 @FunctionalInterface
        BiConsumer<Integer, String> integerStringBiConsumer = (k, v) -> {
            if (k > 1) {
                System.out.println("key大於1,輸出至理名言, 我是" + v);
            }
        };
        map.forEach(integerStringBiConsumer);
        

 

 5 從排序學lambda表達式

    @Test
    public void sortTest(){
        String[] players = {"Xiaoming", "Jack",
                "Cancan", "Tom","Alin"};

        //使用匿名內部類根據 name 排序 players
        //Arrays.sort(players, new Comparator<String>() {
        //    @Override
        //    public int compare(String s1, String s2) {
        //        return (s1.compareTo(s2));
        //    }
        //});
        //for (String player : players) {
        //    System.out.println(player);
        //}


        //使用lambds表達式
        Arrays.sort(players, Comparator.naturalOrder());
        for (String player : players) {
            System.out.println(player);
        }
    }

 

三、Stream操作

 
流的使用包括三件事:
  • 數據源,集合
  • 中間操作,流水線
  • 終端操作,執行流水線,生成結果

其實流水線的背后理念類似於構建器模式,構建器模式就是用來設置一套配置,也就是這里的中間操作,接着調用built方法,也就是這里的終端操作。

 

Stream操作分類
中間操作(Intermediate operations) 無狀態(Stateless) unordered() filter() map() mapToInt() mapToLong() mapToDouble() flatMap() flatMapToInt() flatMapToLong() flatMapToDouble() peek()
有狀態(Stateful) distinct() sorted() sorted() limit() skip()
結束操作(Terminal operations) 非短路操作 forEach() forEachOrdered() toArray() reduce() collect() max() min() count()
短路操作(short-circuiting) anyMatch() allMatch() noneMatch() findFirst() findAny()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

1、流的創建 --- 一般都是通過list或者map生成流的

 

    @Test
    public void streamTest(){
        //直接復值的形式
        Stream stream = Stream.of("a", "b", "c", 23);
        stream.forEach(key -> System.out.println(key));

        System.out.println("===============");
        //通過數組生成
        String[] array = new String[]{"abc", "efg"};
        stream = Arrays.stream(array);
        stream.forEach(key -> System.out.println(key));
        System.out.println("===============");

        //通過list生成
        List<String> list = Arrays.asList(array);
        stream = list.stream();
        stream.forEach(key -> System.out.println(key));
        System.out.println("===============");

        //IntStream、LongStream、DoubleStream
        IntStream stream2 = IntStream.of(1, 2, 3, 3);
        DoubleStream stream4 = DoubleStream.of(1, 2, 3, 3.4);

        stream2.forEach(key -> System.out.println(key));
        stream4.forEach(key -> System.out.println(key));
    }

 

2 測試 --- 跟着我一起做

 實體類

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {

    //學生編號
    private String sNo;
    //學生名字
    private String name;
    //性別
    private String gender;
    // 住宿地址編號
    private Integer addressId;
    // 個人評分
    private Double score;
}

 

測試類

@SpringBootTest
class LambdsStreamApplicationTests {

    static List<Student> students = new ArrayList<>();

    @BeforeEach
    public void init(){
        students.add(new Student("XS1001","大軍","男",1,4.5));
        students.add(new Student("XS1011","河馬","男",2,1.4));
        students.add(new Student("XS1002","小刀","女",1,3.0));
        students.add(new Student("XS1022","柯靈","男",1,3.9));
        students.add(new Student("XS1003","鍾歸","男",2,4.9));
    }


    @Test
    public void test1(){
        //遍歷打印
        students.forEach(System.out::println);
    }

    @Test
    public void filterTest(){
        //去掉頻繁為3以下的學生
        //中間操作,流水線 .filter(student -> student.getScore() >= 3)
        //終端操作 .collect(Collectors.toList());
        List<Student> collect = students.stream().filter(student -> student.getScore() >= 3).collect(Collectors.toList());
        collect.forEach(System.out::println);

        //Student(sNo=XS1001, name=大軍, gender=男, addressId=1, score=4.5)
        //Student(sNo=XS1002, name=小刀, gender=女, addressId=1, score=3.0)
        //Student(sNo=XS1022, name=柯靈, gender=男, addressId=1, score=3.9)
        //Student(sNo=XS1003, name=鍾歸, gender=男, addressId=2, score=4.9)
    }

    @Test
    public void mapTest(){
        //對一個 List<Object> 大部分情況下,我們只需要列表中的某一列,或者需要把里面的每一個對象轉換成其它的對象,這時候可以使用 map 映射
        List<String> collect = students.stream().map(Student::getSNo).collect(Collectors.toList());
        collect.forEach(System.out::println);

        //XS1001
        //XS1011
        //XS1002
        //XS1022
        //XS1003
    }

    @Test
    public void groupTest(){
        // 按照 地址Id 進行分組
        Map<Integer, List<Student>> collect = students.stream().collect(Collectors.groupingBy(Student::getAddressId));
        collect.forEach((k,v)->{
            v.forEach(t->System.out.println("k: "+k+" v: "+t));
        });

        //k: 1 v: Student(sNo=XS1001, name=大軍, gender=男, addressId=1, score=4.5)
        //k: 1 v: Student(sNo=XS1002, name=小刀, gender=女, addressId=1, score=3.0)
        //k: 1 v: Student(sNo=XS1022, name=柯靈, gender=男, addressId=1, score=3.9)
        //k: 2 v: Student(sNo=XS1011, name=河馬, gender=男, addressId=2, score=1.4)
        //k: 2 v: Student(sNo=XS1003, name=鍾歸, gender=男, addressId=2, score=4.9)
    }

    @Test
    public void group2Test(){
        Map<Integer, Double> collect = students.stream().collect(Collectors.groupingBy(Student::getAddressId, Collectors.summingDouble(Student::getScore)));
        collect.forEach((k,v)->System.out.println("k: "+k+" ,v: "+v));

        //k: 1 ,v: 11.4
        //k: 2 ,v: 6.300000000000001
    }

    @Test
    public void sortTest(){
        //按照某個熟悉排序
        students.sort((v1,v2)-> v2.getScore().compareTo(v1.getScore()));
        students.forEach(System.out::println);
    }

    @Test
    public void sortSTest(){
        //流處理不會改變原列表,需要接受返回值才能得到預期結果
        List<Student> collect = students.stream().sorted(Comparator.comparing(Student::getScore).reversed()).collect(Collectors.toList());
        collect.forEach(System.out::println);

        //Student(sNo=XS1003, name=鍾歸, gender=男, addressId=2, score=4.9)
        //Student(sNo=XS1001, name=大軍, gender=男, addressId=1, score=4.5)
        //Student(sNo=XS1022, name=柯靈, gender=男, addressId=1, score=3.9)
        //Student(sNo=XS1002, name=小刀, gender=女, addressId=1, score=3.0)
        //Student(sNo=XS1011, name=河馬, gender=男, addressId=2, score=1.4)
    }

    @Test
    public void sort2STest(){
        //多列排序, score 降序, companyId升序
        List<Student> collect = students.stream().sorted(Comparator.comparing(Student::getAddressId).reversed()
                .thenComparing(Comparator.comparing(Student::getGender).reversed()))
                .collect(Collectors.toList());
        collect.forEach(System.out::println);

        //Student(sNo=XS1011, name=河馬, gender=男, addressId=2, score=1.4)
        //Student(sNo=XS1003, name=鍾歸, gender=男, addressId=2, score=4.9)
        //Student(sNo=XS1001, name=大軍, gender=男, addressId=1, score=4.5)
        //Student(sNo=XS1022, name=柯靈, gender=男, addressId=1, score=3.9)
        //Student(sNo=XS1002, name=小刀, gender=女, addressId=1, score=3.0)
    }

    @Test
    public void reduceSTest(){
        //總分和
        Double reduce = students.stream().parallel().map(Student::getScore).reduce(0d, Double::sum);
        System.out.println(reduce);

        //17.700000000000003
    }

}

 

 
 
 
 

 

 

 

 

 


免責聲明!

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



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