jdk8的新特性總結(一):lambda表達式

jdk8的lambda表達式和StreamAPI能夠簡化以前我們需要重複編寫的代碼,以前一直都是用jdk6,最近一個新項目開始使用jdk8,經過一段時間的使用,着實感受到了jdk8的強大便捷,本文對jdk8的新特性做了一個總結,希望可以幫助大家快速的瞭解並上手jdk8。

一、lambda表達式

lambda表達式讓我們可以將方法體作爲參數進行傳遞,最常見的是匿名內部類的實現,以前我們需要new一個對象,然後實現抽象方法,就像下面這樣:

Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("傳統new線程的方式");
    }
});

而我們用lambda表達式只需要一行代碼,將run方法的方法體傳遞給Thread即可:

Thread thread = new Thread(() -> System.out.println("hello lambda"));

這裏有一個疑問,這種寫法JDK是如何知道調用Thead的哪個構造器的呢?

首先lambda表達式傳遞是一行代碼,jdk會找一個只有一個參數,並且這個參數爲函數式接口的構造方法。來解釋一下什麼叫函數式接口。只有一個抽象方法的接口就叫做函數式接口,函數式接口可以用@FunctionalInterface來標識。而Runnable就是一個函數式接口。我們可以看一下Runnable的源碼:

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

總結:lambda表達式就是括號 + 箭頭 + 方法體。如果方法體只有一行代碼,則可以省略大括號,如果有返回值還可以省略return。lambda常見的格式:

  • (參數1, 參數2) -> 表達式
  • (參數1, 參數2) -> { 一系列表達式 }
  • 參數 -> 表達式

二、函數式接口

接下來我們來描述一個業務場景,通過這個業務場景來繼續瞭解一下函數式接口,假設有一個需求,傳入一個值,經過一系列計算之後返回一個值,這一系列計算是未知的,需要具體調用者去實現,代碼如下:

public class MyTest {
    @Test
    public void test(){
        //這裏爲了演示就直接定義內部類。
        int result1 = handle(10, new HandleInterface() {
            @Override
            public Integer handle(Integer i) {
                //...假裝有一系列業務操作
                return i * 100;
            }
        });
        System.out.println(result1);//輸出1000
        int result2 = handle(10, new HandleInterface() {
            @Override
            public Integer handle(Integer i) {
                //...假裝有一系列業務操作
                return i + 100;
            }
        });
        System.out.println(result2);  //輸出110
        //如同上面一樣,每一種業務操作,我們都會創建一個HandleInterface的實現類來實現具體的業務邏輯
    }

    /**
     * 處理方法,接受被處理的數字和HandleInterface的實現方法(具體業務邏輯)
     * @param num
     * @param h
     * @return
     */
    public int handle(Integer num, HandleInterface h){
        return h.handle(num);
    }
    /**
     * 一般從架構的考慮,我們都會定義一個抽象類,然後每一種計算(也可以說業務場景)我們都會定義一個類
     * 來實現它。
     * 因爲這個類只有一個抽象方法,所以我們也可以加上@Functionalinterface註解標識此接口爲函數式接口
     */
    @FunctionalInterface
    interface HandleInterface {
        public Integer handle(Integer i);
    }
}

而在jdk8中,已經在內部給我們定義好了許多的函數式接口,常用的有Suppiler(供給型),Comsumer(消費型),Function(函數型),Predicate(判斷型)。這些接口都是帶泛型的,有了這些內置的函數接口,我們就不需要自己定義接口了,接下來我們就用內置的函數式接口實現上線的功能:

public class MyTest2 {
    @Test
    public void test(){
        int result1 = handle(10, (e) -> e * 100); 
        System.out.println(result1);  //輸出1000
        int result2 = handle(10, (e) -> e + 100);
        System.out.println(result2);  //輸出110
    }
    /**
     * Function<T, R></>函數接口有兩個泛型, T表示參數類型,R表示返回值類型
     * @param num
     * @param f
     * @return
     */
    public Integer handle(Integer num, Function<Integer, Integer> f) {
        return f.apply(num);
    }
}

三、方法引用

首先我們學習一下方法引用,我們來思考一個問題,在lambda表達式中傳遞的是一段方法體,每次我都要去寫這一段方法體,但是如果我要進行的操作已經有現成的方法了,那麼能不能直接將這個方法傳給lambda表達式呢?答案是可以的,這就是方法引用,方法引用使用雙冒號“::”作爲操作符,主要有以下兩種方式:

  • 實例對象::實例方法名   (因爲右邊是實例方法,所以左邊的實例對象必須是一個已經被new出來的對象)
  • 類::靜態方法 名  (因爲右邊是靜態方法,所以左邊的實例對象寫類名即可)

需要注意的是,實現抽象方法的參數列表,必須與方法引用的方法參數列表保持一致。

接下來我們來寫一些方法引用的例子:

    @Test
    public void test2() {
        //例如1:
        Thread thead1 = new Thread(() -> System.out.println("hellow lambda"));
        //等同於,這裏的System.out返回的是一個PrintStream對象,也就是  實例對象::實例方法名 的寫法
        Thread thead2 = new Thread(System.out::toString);

        //例如2:這裏的BinaryOperator是一個二元操作接口函數
        BinaryOperator<Double> bo1 = (x, y) -> Math.pow(x,y);
        //等同於
        BinaryOperator<Double> bo2 = Math::pow;

        //例如3:比較兩個字符串是否相等
        BiPredicate<String, String> pre1 = (str1, str2) -> str1.equals(str2);
        //等同於
        /**
         * 這種寫法需要好好解釋一下,當需要引用的方法的第一個參數是調用對象,並且第二個參數是需要引用方法
         * 的第二個參數(或無參或多個參數)時,可以採用ClassName::methodName的寫法,這個地方就相當於是
         * "nihao".equals("hellow")。
         * PS:如果有多個參數不知道是否支持這種寫法,有興趣的可以試一下。
         */
        BiPredicate<String, String> pre2 = String::equals;
        pre2.test("nihao", "hellow");
    }

四、構造器引用

構造器引用和方法引用是一個道理,不過右邊不需要寫方法名,只需要寫new就可以了,格式:ClassName::new,接下來我們寫幾個例子。

    @Test
    public void test3() {
        //例如1
        Function<Integer, Double> fun1 = (i) -> new Double(i);
        //等同於,這時候會調用Double類中有一個參數爲Interger的構造器方法
        Function<Integer, Double> fun2 = Double::new;
        //例如2,定義長度爲i的數組
        Function<Integer, Integer[]> fun3 = (i) -> new Integer[i];
        //等同於
        Function<Integer, Integer[]> fun4 = Integer[]::new;
    }

總結一下:

  1. lambda表達式左邊的括號中可以寫入參,多個用逗號隔開,當只有一個參數時,小括號也可以不寫,並且可以省略參數類型,JDK會通過上下文進行類型推斷,推算出參數類型;
  2. lambda表達式的右邊寫具體的方法內容,如果只有一行代碼,則可以省略{}和return語句;
  3. 函數式接口表示只有一個抽象方法的接口,並且可以用@FunctionalInterface標識;
  4. Supplier<T>表示供給型接口,沒有入參,通過返回T類型結果,抽象方法:T get(),當
  5. Comsumer<T>表示消費型接口,有一個T類型的入參,沒有返回值,抽象方法:void accpet(T t),當你需要有兩個入參時,你可以使用它的子類BiConsumer<T, U>。
  6. Function<T, R>表示函數型接口,有一個T類型的入參,一個R類型的返回值,抽象方法:R apply(T t),同樣,當你有兩個入參時,你也可以使用它的子類,BiFunction<T, U, R>
  7. Predicate<T>表示判斷型接口,有一個T類型的入參,一個boolean類型的返回值,抽象方法:boolean test(T t),它也有多個入參類型的BiPredicate<T, U>
  8. 內置的函數型接口都放在java.util.function包下,裏面還有很多不帶泛型的接口,當你確定參數或返回值的數據類型時,可以使用這些函數接口。
  9. 當抽象方法的參數列表和引用方法的參數列表參數一致時,可以採用方法引用,方法引用的寫法 實例對象::方法名,類::靜態方法名。當第一個參數是方法的調用者,第二個參數(或無參)是方法的參數時,可以採用ClassName::methodName形式的寫法。
  10. 構造器引用:ClassName::new和 ClassName[]::new。

以上就是lambda表達式的所有內容,下一篇文章我們一起來學習強大的StreamAPI。

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