一、前言
本文,前段是原理,后半段是案例,如果懶得看原理的朋友,可以直接跳到案例
敲黑板,跟我邊做邊學,直接到案例那一段,非常詳細。
什么是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 } }