JDK8 新特性學習(lambda)

1. Lambda表達式

1.1 函數式編程思想概述

傳統的面向對象編程,函數的執行更注重的是根據對象的某種方法執行某個函數,比較依賴於對象。而函數式編程,將對象與函數的這種聯繫給優化了,不需要我們自己來創建對象和調用函數,換句話說JVM內部會幫我推導出對象於函數之間的聯繫,這樣可以簡化我們的編碼量,提高開發效率。

1.2 多線程開發中遇到的冗餘代碼

回顧傳統線程創建的三種方式:

  1. 繼承Thread,重寫run()
public class TestExtends extends Thread{
    @Override
    public void run() {
        System.out.println("線程1....");
    }
}

調用:

//1.繼承Thread
TestExtends anExtends = new TestExtends();
anExtends.start();
  1. 實現Runnable接口,重寫run()
public class TestRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("線程2....");

    }
}

調用:

//2.實現Runnable
TestRunnable runnable = new TestRunnable();
new Thread(runnable).start();
  1. 匿名內部類實現
 //3. 使用匿名內部類的實現
 new Thread(new Runnable() {
     @Override
     public void run() {
         System.out.println("線程3....");
     }
 }).start();

可以發現,三種方式同樣做了一件事,重寫run方法,而且它們需要我們手都去創建Runnable的實現類,此時我們想,我們應該更注重線程體裏面的內容,而不是去關注其它的細節,引出lambda表達式。

1.3 使用lambda表達式優化

使用 ()——>表達式來簡化代碼,只需要3行代碼即可,()根據情況傳參可以傳參

 //4.lambda表達式
 new Thread(()->{
     System.out.println("線程4....");
 }).start();

1.4 幾個練習

1.4.1 使用lambda表達式(無參無返回)

題目:給定一個廚子Cook接口,內含唯一的抽象方法makeFood,且無參無返回值
如下:

public interface Cook {
    public abstract void makeFood();
}

在下面的代碼中,使用lambda表達式的標準格式調用invokeCook方法,打印字符串

public class InvokeCook {

    public static void main(String[] args) {
        // TODO 請在此使用Lambda【標準格式】調用invokeCook方法
    }

    public static void invokeCook(Cook cook){
        cook.makeFood();
    }
}

解答

public class InvokeCook {

    public static void main(String[] args) {
        // TODO 請在此使用Lambda【標準格式】調用invokeCook方法
        InvokeCook.invokeCook(()->{
            System.out.println("該喫飯了....");
        });
    }
    public static void invokeCook(Cook cook){
        cook.makeFood();
    }
}

1.4.2 使用lambda表達式(有參無返回)

回顧對象比較大小,使用Comparator接口

public class PersonComparator {
    public static void main(String[] args) {
        Person[] persons = {new Person("周杰倫",45),
        new Person("林俊杰",33),new Person("成龍",56)};

        Comparator<Person> comparator = new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o1.getAge() - o2.getAge();
            }
        };
        //排序
        Arrays.sort(persons,comparator);
        for (Person person : persons) {
            System.out.println(person);
        }
    }
}

使用lambda表達式優化

public class LambdaComparator {
    public static void main(String[] args) {
        Person[] persons = {new Person("周杰倫",45),
                new Person("林俊杰",33),new Person("成龍",56)};

        Arrays.sort(persons, ((Person o1,Person o2)->{
            return o1.getAge() - o2.getAge();
        }) );
        //遍歷
        for (Person person : persons) {
            System.out.println(person);
        }
    }
}

1.4.3 使用lambda表達式(有參有返回)

問題:定義一個計算器接口類,實現兩數之和

public interface Calculator {
    public int sum(int a,int b);
}

在下面的代碼中,請使用Lambda的標準格式調用 invokeCalc 方法,完成120和130的相加計算:

public class InvokeCalculator {
    public static void main(String[] args) {
    // TODO 請在此使用Lambda【標準格式】調用invokeCalc方法來計算120+130的結果ß
    }
    public static void calculator(int a,int b,Calculator calculator){
        int result = calculator.sum(a, b);
        System.out.println(result);
    }
}

解答

public class InvokeCalculator {
    public static void main(String[] args) {
    // TODO 請在此使用Lambda【標準格式】調用invokeCalc方法來計算120+130的結果ß
        InvokeCalculator.calculator(120,130,(int a,int b)->{
            return a+b;
        });

    }
    public static void calculator(int a,int b,Calculator calculator){
        int result = calculator.sum(a, b);
        System.out.println(result);
    }
}

1.5 繼續優化lambda表達式

依據可推導即可省略
如省略了{}和;

 InvokeCook.invokeCook(()->
     System.out.println("該喫飯了....")
 );

省略規則

  1. 小括號內的參數類型可以省略
  2. 如果小括號內只有一個參數,那麼小括號也可以省略
  3. 如果大括號內只有一行,無論是否有返回值,均可以省略{},return關鍵字和尾部分號。

上面代碼的一些省略:

 InvokeCalculator.calculator(120,130,( a, b)->
      a+b);
}
Arrays.sort(persons, (( o1, o2)->
     o1.getAge() - o2.getAge()) );

1.6 lambda表達式的使用前提

lambda表達式的語法非常簡潔,完全沒有面向對象複雜的束縛,但是使用時有幾個問題需要注意:

  1. 使用lambda表達式必須具有接口,要求接口中有且只有一個抽象方法。無論是JDK內置的Runnable,Comparator還是自定義的接口,當接口中的抽象方法唯一時,纔可以使用lambda表達式。
  2. 使用lambda表達式必須具有上下文推斷,也就是方法的參數或局部變量必須爲lambda對應的接口類型,纔可以使用lambda作爲該接口的實例。

備註:有且只有一個抽象方法的接口,稱爲函數式接口

1.7 函數式接口

1.7.1 概念

有且只有一個抽象方法的接口,稱爲函數式接口。

函數式接口,即適用於函數式編程場景的接口,而Java函數式編程體現就是lambda,所以函數式接口可以適應於lambda使用的接口,只有確保接口中只有一個抽象方法,lambda才能順序進行推導。

1.7.2 語法糖

語法糖是指使用更加方便,但原理不變的代碼語法,如我們在遍歷集合的時候可以使用迭代器,也可以使用for-each遍歷,但是迭代器的語法相對複雜,for-each的遍歷底層也是使用迭代器,所以可以成for-each是迭代器的語法糖。從應用層面上講,lambda表達式是匿名內部類的語法糖,但是兩者在原理上又有點不同

1.7.3 格式

修飾符 interface 接口名{
	pubic abstract 返回值 方法名(參數列表);
	//其它非非抽象函數
}

其中pubic abstract也可以省略。

1.7.3 註解@FunctionalInterface

作用是被FuncationalInterface標註的接口是一個函數式接口,即接口內只能包含一個抽象的方法,否則編譯失敗。
在這裏插入圖片描述
包含兩個:
在這裏插入圖片描述

2. 函數式編程

2.1 Lambda的延遲執行

由一個日誌日誌案例來引出:

public class TestLogger {

    public static void logger(int leave,String msg){
        System.out.println(msg);   //liuzeyuduyanting
        if( leave == 1){
            System.out.println(msg);
        }
    }

    public static void main(String[] args) {
        String msg1 = "liuzeyu";
        String msg2 = "duyanting";

        logger(2,msg1 + msg2);
    }
}

上面是一個打印日誌信息的案例,目的是有且只有leave = 1的時候,纔打印輸出拼接字符串。但是我們輸入leave = 2的時候,也進行了字符串的拼接,性能浪費了。
使用lambda表達式優化

@FunctionalInterface
public interface LoggerInterface {
    public String logger();
}
public class TestLogger {

    public static void logger(int leave,LoggerInterface myInterface){
        if(leave == 1){
            System.out.println(myInterface.logger());
        }
    }

    public static void main(String[] args) {
        String msg1 = "liuzeyu";
        String msg2 = "duyanting";
        logger(2,()->{
            System.out.println("lambda延遲了...");
            return msg1 + msg2;
        });
    }
}

此時字符串拼接的地方在lambda表達式的內部,可以做到延遲拼接的作用,提高了性能。

2.2 常用的函數式接口

JDK提供了大量常用的函數式接口,主要在java.util.function包中被提供,下面式幾個常用的接口。

2.2.1 Supplier接口

java.util.function.Supplier接口中只包含了一個無參的抽象方法,T get(),泛型傳什麼,get()就返回什麼,常適用於生產環境下。

public class SupplierDemo1 {

    public static String fun1(Supplier<String> supplier){
        return  supplier.get();
    }

    public static void main(String[] args) {
//        String value = fun1(() -> {
//            return "liuzeyu" + "duyanting";
//        });
        //簡化代碼:
        String value = fun1(() ->
           "liuzeyu" + "duyanting");

        System.out.println(value);  //liuzeyuduyanting
    }
}

練習:求數組中最大值

public class SupplierDemo2 {

    public static  Integer getMax(Supplier<Integer> supplier){
        return supplier.get();
    }

    public static void main(String[] args) {
        int[] arr = {11,343,42,547,12};

        int maxValue = getMax(()->{
            int max = arr[0];
            for (int i:
                    arr) {
                if( i > max){
                    max = i;
                }
            }
            return max;
        });
        System.out.println(maxValue); //547
    }
}

2.2.2 Consumer接口

  1. 接口概述
@FunctionalInterface
public interface Consumer<T> {

    void accept(T t);
    
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

Consumer接口的作用剛好了Supplier相反,Supplier的作用是泛型接收一個參數類型,就可以返回數據,主要用於生產環境下,因爲它不會對數據進行修改,而是直接返回。而Consumer接口可以修改數據,使用數據,常用於消費環境。

  1. 抽象方法accept

accept方法作用是可以消費一個接收泛型的數據

public class ConsumerDemo1 {

    public static void fun1(String str,Consumer<String> con){
        con.accept(str);
    }

    public static void main(String[] args) {

        fun1("Hello",(str)->{
            String s = new StringBuilder(str).reverse().toString();
            System.out.println(s);  //olleH
        });
    }
}

  1. 默認方法andThen

如果一個方法的參數和返回值都是Consumer類型,那麼就可以實現效果,首先做一個動作,然後再做一個動作,實現兩個動作的組合,這個時候就需要用到Consumer接口中的andThen方法。
實現組合需要用到多個lambda表達式,andThen方法的語義是進行一步一步操作,例如:

public class ConsumerDemo2 {

    public static void convert(String string, Consumer<String> con1, Consumer<String> con2){
        con1.andThen(con2).accept(string);  //先執行動作con1再執行動作con2
    }

    public static void main(String[] args) {
        convert("Hello",(string)->{
            String lowerCase = string.toLowerCase();
            System.out.println(lowerCase); //hello
        },(string)->{
            String upperCase = string.toUpperCase();
            System.out.println(upperCase);  //HELLO
        });
    }
}
  1. 實現格式化數據

需求:將數據String[] info = {"劉澤煜,22","杜丫頭,20"};數組中的每個信息,格式化爲姓名:年齡,例如 劉澤煜:22

public class ConsumerDemo3 {

    public static  void form(String[] strs, Consumer<String>con1,Consumer<String> con2){
        //將數組中的每一個元素進行
        for (String str : strs) {
            con1.andThen(con2).accept(str);
        }
    }

    public static void main(String[] args) {
        String[] info = {"劉澤煜,22","杜丫頭,20"};
        form(info,(string)->{
            String name = string.split(",")[0];
            System.out.print(name+":");
        },(string)->{
            String age = string.split(",")[1];
            System.out.println(age);
        });
    }
}

2.2.3 Predicate接口

我們需要對某個數據進行判斷,使用抽象方法test(),然後得到布爾值,這個時候可以使用Predicate接口,Predicate也是java.util.function報下的類

  1. 抽象方法test()
public class PredicateDemo1 {
    public static void testLong(String str, Predicate<String> predicate){
        boolean test = predicate.test(str);
        System.out.println(test);  //true
    }

    public static void main(String[] args) {
        testLong("ILoveJava",(String str)->{
            return str.length() > 5;
        });
    }
}

  1. 默認方法and
    and函數表示並且,如果兩個條件都滿足就返回true,否則false

舉例:判斷字符串的長度是否大於5,第一個字符是不是爲L

public class PredicateDemo2 {
    public static void testAnd(String str, Predicate<String> p1,Predicate<String> p2){
        boolean test = p1.and(p2).test(str);
        System.out.println(test);
    }

    public static void main(String[] args) {
        testAnd("Liuzeyu",(String str)->{
            return str.length() > 5;
        },(String str)->{
            return  str.charAt(0) == 'L';
        });
    }
}

  1. 默認方法or
    或者的意思,滿足條件之一即可返回true
  2. 默認方法negate
    取反。

練習
數組中有多條姓名+性別信息,請通過Predicate接口實習信息的篩選,篩選到ArrayList集合中,需要同時滿足以下條件:

  • 必須爲女生
  • 名字爲4個字
public class PredicateDemo3 {

    public static void main(String[] args) {
        String[] infos = { "迪麗熱巴,女", "古力娜扎,女", "馬爾扎哈,男", "趙麗穎,女" };
        ArrayList<String> list = getArrayList(infos, (String str) -> {
            return str.split(",")[0].length() == 4;
        }, (String str) -> {
            return str.split(",")[1].equals("女");
        });

        System.out.println(list);
    }

    public static ArrayList<String> getArrayList(String[] strs, Predicate<String> p1,Predicate<String> p2){
        ArrayList<String> info = new ArrayList<>();
        for (String str : strs) {
            boolean test = p1.and(p2).test(str);
            if(test){
                info.add(str);
            }
        }
        return info;
    }
}

2.2.4 Function接口

Function接口實現的功能是將一種類型轉換成另一種類型,使用的是內部的抽象方法R apply(T t); t是要轉換的對象類型,R是轉換後的對象類型

  1. 抽象方法apply
    舉例:使用Function接口將String類型轉成Integer類型
public class FunctionDemo1 {

    public static  Integer convert(String str, Function<String,Integer> function){
        Integer apply = function.apply(str);
        return apply;
    }

    public static void main(String[] args) {
        Integer integer = convert("123213", (String str) -> {
            return  Integer.parseInt(str);
        });
        System.out.println(integer+10);
    }
}
  1. 默認方法andThen

使用andThen進行組合操作
舉例:將String類型的123轉換成Integer類型後,將結果加10,結果再轉換成String類型

public class FunctionDemo2 {

    public static String convert(String str,
                               Function<String,Integer> fun1,Function<Integer,String> fun2){
        return  fun1.andThen(fun2).apply(str);
    }

    public static void main(String[] args) {
        String convert = convert("123", (String str) -> {
            return Integer.parseInt(str) + 10;
        }, (Integer integer) -> {
            return String.valueOf(integer);
        });

        System.out.println(convert);
    }
}

  1. 練習
    請使用 Function 進行函數模型的拼接,按照順序需要執行的多個函數操作爲:

    String str = “趙麗穎,20”;

    1. 將字符串截取數字年齡部分,得到字符串;
    2. 將上一步的字符串轉換成爲int類型的數字;
    3. 將上一步的int數字累加100,得到結果int數字。
public class FunctionDemo3 {
    public static String convert(String str,
                               Function<String,String> fun1,
                               Function<String,Integer> fun2,
                               Function<Integer,String> fun3){
        return fun1.andThen(fun2).andThen(fun3).apply(str);
    }

    public static void main(String[] args) {
        String str = "趙麗穎,20";
        String convert = convert(str, (String s) -> {
            return s.split(",")[1];
        }, (String s) -> {
            return Integer.parseInt(s) + 100;
        }, (Integer i) -> {
            return String.valueOf(i);
        });

        System.out.println(convert); //120
    }
}

3. Stream流

談到流,可能第一時間想到的是我們之前學過的IO流,但實際上Stream流和IO流並不是一個東西,Stream流是JDK 1.8引入的,得益於lambda表達式所帶來的函數式編程,目的是解決集合類存在的弊端。

3.1 傳統集合類存在的弊端

需求:如果要完成:

  1. 首先篩選所有姓張的人;
  2. 然後篩選名字有三個字的人;
  3. 最後進行對結果進行打印輸出。

使用傳統的集合類操作的話:

public class StreamDemo1 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("張無忌");
        list.add("周芷若");
        list.add("趙敏");
        list.add("張強");
        list.add("張三丰");
        //首先篩選所有姓張的人;
        List<String> list2 = new ArrayList<>();
        for (String s : list) {
            if(s.startsWith("張")){
                list2.add(s);
            }
        }
        //然後篩選名字有三個字的人;
        List<String> list3 = new ArrayList<>();
        for (String s : list2) {
            if(s.length() == 3){
                list3.add(s);
            }
        }
        //最後進行對結果進行打印輸出。
        for (String s : list3) {
            System.out.println(s);
        }
    }
}

可見,代碼量繁重,而且冗餘,用到了多個循環,很影響性能。

3.2 Stream流可以解決的弊端

public class StreamDemo2 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("張無忌");
        list.add("周芷若");
        list.add("趙敏");
        list.add("張強");
        list.add("張三丰");

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

從代碼量來看,確實少了很多,變得更加簡潔了。

3.3 Stream流思想的簡單概述

切勿將此處的流與IO流混爲一談,這是兩個不同的東西,Stream流類似於車間的流水線,每一個物品經歷流水線可能會經過一些步驟:如加工步驟,包裝,拆分…等等步驟。集合元素在Stream上也會同樣盡力一些處理步驟,這個過程集合元素本身沒有發生改變,類似於copy一個集合的副本放在流水線上進行一些操作,在Stream中不會存儲任何元素,它也類似於來自於數據源額元素隊列,數據源可以是集合,數組等。
與之前的Collection操作不同,Stream流還有兩個基礎的特徵:

  • Pipelining:中間操作每次都會返回流本身,這樣多個操作可以串聯成一個管道,如同流式風格。
  • 內部迭代:以前對集合的遍歷一般都是採用迭代器或增強for循環,採用的的外部迭代方式,而Stream流提供的是內部迭代的方式,流可以直接調用遍歷方法。

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

3.4 根據不同集合類型獲取流

public class StreamDemo1 {
    public static void main(String[] args) {
        //根據Collectioin獲取的流
        List<String> list = new ArrayList<String>();
        Stream<String> stream1 = list.stream();
        Set<String> set = new HashSet<>();
        Stream<String> stream2 = set.stream();
        //根據Map獲取流
        Map<String,String> map = new HashMap<>();
        Set<String> keySet = map.keySet();
        Stream<String> stream3 = keySet.stream();
        Set<Map.Entry<String, String>> entrySet = map.entrySet();
        Stream<Map.Entry<String, String>> stream4 = entrySet.stream();
        Collection<String> values = map.values();
        Stream<String> stream5 = values.stream();
        //根據數組獲取流
        String[] names = new String[3];
        names[0]="xxx";
        names[1]="yyy";
        Stream<String> stream6 = Arrays.stream(names);

    }
}

運行結果:
在這裏插入圖片描述

3.5 流的常用方法

流模型的操作很豐富,主要的操作方法可以分爲兩類:

  • 延遲方法:返回值類型仍然是Stream類型,因此支持鏈式調用,除了終結方法外,其它的方法都屬於延遲方法。
  • 終結方法:返回值不再是Stream類型的方法,不支持鏈式調用,終結方法有foreach和count。

3.5.1 forEach:逐一處理

public class StreamDemo2 {
    public static void main(String[] args) {
        String[] names = {"liuzeyu","duyanting","jay"};
        Stream<String> stream = Stream.of(names);

//        stream.forEach((String name)->{
//            System.out.println(name);
//        });
        stream.forEach(name-> System.out.println(name));
    }
}

運行結果:
在這裏插入圖片描述

3.5.2 count:統計個數

public class StreamDemo3 {
    public static void main(String[] args) {
        Integer[] ids = {1,2,3,5,6};
        Stream<Integer> stream = Stream.of(ids);
        long count = stream.count();
        System.out.println(count);  //5
    }
}

3.5.3 filter:過濾

用到Predicate<T>接口函數

public class StreamDemo4 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("張無忌");
        list.add("周芷若");
        list.add("趙敏");
        list.add("張強");
        list.add("張三丰");

        Stream<String> stream = list.stream();
        stream.filter((String name)->{return name.startsWith("張");})
                .filter((String name)->{return name.length() == 3;})
                .forEach((String name)->{
                    System.out.println(name);
                });
    }
}

filter每個操作都會返回一個新的Stream對象。
運行結果:
在這裏插入圖片描述

3.5.4 map:映射

用到Function<T,R>函數式接口

public class StreamDemo5 {
    public static void main(String[] args) {
        Integer[] ids = {1,2,3,5,6};
        //將Integer類型轉換成String
        Stream<Integer> stream = Stream.of(ids);
        Stream<String> stringStream = stream.map((Integer id) -> {
            return String.valueOf(id);
        });
        stringStream.forEach(name-> System.out.println(name));
    }
}

運行結果:
在這裏插入圖片描述

3.5.5 limit:提取前幾個

public class StreamDemo6 {

    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("張無忌");
        list.add("周芷若");
        list.add("趙敏");
        list.add("張強");
        list.add("張三丰");

        Stream<String> stream = list.stream();
        Stream<String> limit = stream.limit(3);
        limit.forEach(name-> System.out.println(name));
    }
}

運行結果:
在這裏插入圖片描述

3.5.6 skip:跳過前幾個

public class StreamDemo7 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("張無忌");
        list.add("周芷若");
        list.add("趙敏");
        list.add("張強");
        list.add("張三丰");

        Stream<String> stream = list.stream();
        Stream<String> skip = stream.skip(2);
        skip.forEach(name-> System.out.println(name));
    }
}

運行結果:
在這裏插入圖片描述

3.5.7 concat:組合

public class StreamDemo8 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("張無忌");
        list.add("周芷若");
        list.add("趙敏");
        list.add("張強");
        list.add("張三丰");

        Integer[] ids = {1,2,3,5,6};

        Stream<String> stream1 = list.stream();
        Stream<Integer> stream2 = Stream.of(ids);
        Stream<? extends Serializable> concat = Stream.concat(stream1, stream2);
        concat.forEach(name-> System.out.println(name));
    }
}

運行結果:
在這裏插入圖片描述

3.6 練習

需求:

  1. 第一個隊伍只要名字爲3個字的成員姓名;存儲到一個新集合中。
  2. 第一個隊伍篩選之後只要前3個人;存儲到一個新集合中。
  3. 第二個隊伍只要姓張的成員姓名;存儲到一個新集合中。
  4. 第二個隊伍篩選之後不要前2個人;存儲到一個新集合中。
  5. 將兩個隊伍合併爲一個隊伍;存儲到一個新集合中。
  6. 根據姓名創建 Person 對象;存儲到一個新集合中。
  7. 打印整個隊伍的Person對象信息。
public class Person {
    private String name;
    public Person() {
    }
    public Person(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                '}';
    }
    ....代碼省略
public class ListDemo {
    public static void main(String[] args) {
        //第一支隊伍
        ArrayList<String> one = new ArrayList<>();
        one.add("迪麗熱巴");
        one.add("宋遠橋");
        one.add("蘇星河");
        one.add("石破天");
        one.add("石中玉");
        one.add("老子");
        one.add("莊子");
        one.add("洪七公");
        //第二支隊伍
        ArrayList<String> two = new ArrayList<>();
        two.add("古力娜扎");
        two.add("張無忌");
        two.add("趙麗穎");
        two.add("張三丰");
        two.add("尼古拉斯趙四");
        two.add("張天愛");
        two.add("張二狗");

        Stream<String> stream1 = one.stream();
        Stream<String> stream2 = two.stream();

        //利用流思想
        //隊伍1
        Stream<String> limit = stream1.filter(name -> name.length() == 3).limit(3);
        //隊伍2
        Stream<String> skip = stream2.filter(name -> name.startsWith("張")).skip(2);
        //合爲一隻隊伍
        Stream<String> concat = Stream.concat(limit, skip);
        //存儲到Person對象中
        List<Person> persons = new ArrayList<>();
        concat.forEach(name->persons.add(new Person(name)));
        for (Person person : persons) {
            System.out.println(person);
        }
    }
}

運行結果:
在這裏插入圖片描述

4. 方法引用(不好理解)

在使用lambda表達式的時候,我們實際傳遞進去的代碼就是一種解決方案,拿什麼參數就做什麼操作,那麼考慮另外一種情況,如果lambda中所指定的操作方案,已經有地方存在相同的方案,那是否有必要重複再寫一份呢?

4.1 方法引用

public class MethodRef {
    public static void printString(Printable printable){
        printable.printMsg("Hello Java");
    }

    public static void main(String[] args) {
//        printString((String str)->{
//            System.out.println(str);
//        });
        //使用方法引用
        printString(System.out::print);
    }
}
interface Printable{
    void printMsg(String msg);
}

printString(System.out::print);傳遞的參數System.out::print,很奇怪是吧,爲什麼傳遞這樣一個參數,就可以替代(String str)->{ System.out.println(str); }

  • 使用lambda表達式獲取到傳遞的參數後,再將它傳遞給System.out.println(str);
  • 而使用方法引用,是直接讓System.out中的println來取代lambda表達式,兩種寫法執行效果完全一致,而第二種方法引用的寫法複用了已有的方案,更加簡潔。

注意:lambda表達式中傳遞的參數一定是方法引用中那個方法可以接收的參數類型,否則會拋出異常。

再看一個例子:

public class MethodRef {
    public static void printString(Printable printable){
        printable.printMsg(3434);
    }

    public static void main(String[] args) {
//        printString((Integer i)->{
//            System.out.println(i);
//        });
        //使用方法引用
        printString(System.out::print);
    }
}
interface Printable{
    void printMsg(Integer msg);
}

可見無論使用lambda表達式還是使用方法引用,都是根據上下文推導出,接收的相應參數類型,來輸出相應的參數值。

4.2 對象名引用成員方法

這也是一種常見的用法,準備好MethodRefObject類

public class MethodRefObject {
    public void printUpperCase(String msg){
        System.out.println(msg);
    }
}

函數式接口

interface Printable2{
    void print(String msg);
}

那麼當需要使用printUpperCase方法來替代Printable2 接口的lambda表達式的時候,就已經具有了MethodRefObject類的對象實例,所以可以通過對象類引用方法名:

public class MethodRef2 {
    public static void pringString(Printable2 printable2){
        printable2.print("Hello");
    }

    public static void main(String[] args) {
        //1.使用lambda表達式
//        pringString((String msg)->{
//            MethodRefObject mfo = new MethodRefObject();
//            mfo.printUpperCase(msg);
//        });
        //2.使用對象的方法引用
        MethodRefObject mfo = new MethodRefObject();
        pringString(mfo::printUpperCase);

    }
}

4.3 通過類型引用靜態方法

同樣的方法,當需要使用abs來替代Calcable 接口lambda表達式的時候,由於abs是靜態方法,於是可以使用類名調用

public class MethodRef3 {
    public static void  method(int num,Calcable cal){
        System.out.println(cal.calc(num));
    }
    public static void main(String[] args) {
        //1.使用lambda表達式
//        method(10,(int num)->{
//            return  Math.abs(num);
//        });
        //method(-10, num-> Math.abs(num));

        //2. 使用方法引用
        method(-1,Math::abs);
    }

}
@FunctionalInterface
interface Calcable{
    int calc(int sum);
}

4.4 super引用父類的方法

super引用父類的方法:super:父類方法
準備父類:

public class Human {
    void sayHello(){
        System.out.println("Hello....");
    }
}

準備函數式接口:

@FunctionalInterface
public interface Greetable {
    void greet();
}

準備子類:

public class Man extends Human {
    @Override
    void sayHello() {
        super.sayHello();
    }

    //定義method,傳遞參數
    public void method(Greetable g){
        g.greet();
    }

    public void show(){
        //調用method方法,使用lambda表達式
        method(()->{
            //創建父類Human對象,調用sayHello方法
            new Human().sayHello();
        });
        //使用方法引用
        method(super::sayHello);
    }


    public static void main(String[] args) {
        new Man().show();
    }

}

super::sayHello的作用和使用lambda表達式一樣。

4.5 this引用本類的成員方法

this引用本類的成員方法採用: this:方法名

函數式接口:

@FunctionalInterface
public interface Richeable {
    void buy();
}

Husband 類:

package methodreference.thisRef;

public class Husband {
    private  void marry(Richeable r){
        r.buy();
    }

    private void byHouse(){
        System.out.println("買套新房...");
    }

    public void beHappy(){
        marry(()->{
            byHouse();          //買套新房...
        });
        marry(()->{
            this.byHouse();     //買套新房...
        });
        //使用方法引用
        marry(this::byHouse);  //買套新房...
    }


    public static void main(String[] args) {
        new Husband().beHappy();
    }
}

4.6 類的構造器的引用

構造器的引用採用的是:類名:new
準備Person 實體類:

public class Person {
    private String name;
    public Person(String name) {
        this.name = name;
    }
    public Person() {
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

準備函數式接口:

@FunctionalInterface
public interface PersonBuilder {
    public Person buildPerson(String name);
}

測試類:

public class Test {

    public static void printName(String name,PersonBuilder pb){
        System.out.println(pb.buildPerson(name).getName());
    }

    public static void main(String[] args) {
        printName("張三丰",(String name)->{
            return  new Person(name);
        });
        //使用方法引用
        printName("張無忌",Person::new);
    }
}

4.7 數組構造器的引用

數組也是Object的子類,所以同樣具有構造器,只是引用的方法有所不同,採用的是:數組類型[]:new

函數式接口:

@FunctionalInterface
public interface ArrayBuilder {
    //根據數組長度創建整形數組
    int[] buidArray(int len);
}

測試類:

public class Test {
    private static int[] initArray(int len,ArrayBuilder ab){
        return ab.buidArray(len);
    }

    public static void main(String[] args) {
        int[] array1 = initArray(10, (len) -> {
            return new int[len];
        });

        int[] array2 = initArray(12, int[]::new);
        System.out.println(array1.length);
        System.out.println(array2.length);
    }
}

參考:黑馬視頻+筆記

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