初學Java8 Lambda表達式

學習筆記

作爲學習筆記我就不寫歷史啥的了,完全隨心記。
首先,Java lamda的標準寫法
(param1, param2) -> {expression};
param爲匿名內部類中的函數參數,expression爲函數裏面的所有語句,常寫的例子

new Thread(new Runnable() {
            @Override
            public void run() {
                // doSomething
                System.out.println("test");
            }
        }).start();

這裏匿名內部類可省略,編譯器會自動推斷,函數沒有參數就可直接使用括號,函數內部語句直接寫到花括號中,lambda表達式如下

new Thread(() -> {
            System.out.print("test");
        }).start();

接下來又是很重要的一點
如果參數只有一個,小括號可以去掉 即爲
param -> {expression};
比如

jButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                System.out.println("Action performed");
                e.getActionCommand();
            }
        });

這裏只有ActionEvent類的一個對象e爲參數,裏面有兩行代碼,所以可以省略掉參數外面的括號,而且參數可以自己定義爲任何合法的標識符,但是在函數內部引用的時候需要與你定義的參數保持一致,這裏就變成了

jButton.addActionListener(w-> {
                System.out.println("Action performed");
                w.getActionCommand();
            });

好的,語法還可以進一步簡化
如果表達式只有一行,大括號,可以去掉,並且這一行最後的分號結尾,要去掉
最開始寫的新建線程的lambda表達式可以進一步簡化成這樣

new Thread(() -> System.out.println("test")).start();

那麼,這是java8的新特性,什麼情況下能使用lambda表達式呢?我們看到的形式都是在創建匿名內部類的時候使用lambda表達式,創建形式就是new一個某個接口類型的對象,然後在內部類中實現接口的方法。那麼,這些接口都有什麼特點呢,通過Idea打開Runnable接口的定義,會發現接口上面有這麼一個註解
@FunctionalInterface
即函數式接口,那我們再看看這個接口的定義吧

An informative annotation type used to indicate that an interface type declaration is intended to be a functional interface as defined by the Java Language Specification.
Conceptually, a functional interface has exactly one abstract method. Since {@linkplain java.lang.reflect.Method#isDefault() default methods} have an implementation, they are not abstract. If an interface declares an abstract method overriding one of the public methods of {@code java.lang.Object}, that also does not count toward the interface’s abstract method count since any implementation of the interface will have an implementation from {@code java.lang.Object} or elsewhere

Note that instances of functional interfaces can be created with lambda expressions, method references, or constructor references.

If a type is annotated with this annotation type, compilers are required to generate an error message unless:

  • The type is an interface type and not an annotation type, >enum, or class.
  • The annotated type satisfies the requirements of a functional interface.

However, the compiler will treat any interface meeting the definition of a functional interface as a functional interface regardless of whether or not a {@code FunctionalInterface} annotation is present on the interface declaration.

從這段解釋裏面我們可以提取到幾個要點

  • 首先這個註解是定義在接口上面的
  • 第二,接口由這個註解定義的接口應該有且僅有一個抽象方法,但是由於Java8引入了接口的default方法,default方法有實現,所以不算抽象方法,如果接口覆寫了object的一個public方法,也不計算在這個抽象方法的名額裏面,因爲所有的接口的實現都會實現object的方法。
  • 第三,函數式接口的實例可以lambda expressions, method references, or constructor references的形式出現
  • 第四,不在以上範圍內的接口如果使用了FunctionalInterface註解,編譯器會報錯
  • 最後,雖然有些接口沒有加上FunctionalInterface註解,但是編譯器會把滿足函數式接口的定義的接口看作FunctionalInterface,也就是說這些接口的實例也可以用lambda表達式的形式來寫。

也就是說,所有滿足函數式接口的接口的實例都可以變爲lambda表達式,不滿足的則會在創建的時候報錯。
以下定義的接口就滿足函數式接口的定義

public interface MyTest {
    String[] test(String str);

    default void test2() {
        System.out.println("test");
    }
    String toString();
}

雖然沒有加上FunctionalInterface註解,但是在使用的時候還是可以使用Lambda表達式

public class MyTestMain {
    public static void main(String[] args) {
        // MyTest.test(str)常規寫法;
        String[] test = test(new MyTest() {
            @Override
            public String[] test(String str) {
                return str.split("");
            }
        });
        //變爲lambda表達式
        test(s->s.split(""));
        for (String s : test) {
            System.out.println(s);
        }
    }

    public static String[] test(MyTest test) {
        return test.test("strd");
    }
}

到這裏就可以使用lambda表達式寫很多有意思的代碼啦,仔細觀察,java集合裏面好多的匿名內部類都可以使用lambda表達式來簡寫,我們看了函數式接口用lambda表達式來表示的,當然要介紹一下使用方法引用的形式咯
如果,lambda 表達式只有一個參數,表達式只有一個, 且表達式調用了這個的參數本身,那麼這個參數可以省略,並且變成方法引用的形式
對象::方法名
對象爲表達式中的對象,方法爲該對象調用的方法

appleStore.forEach(apple -> System.out.println(apple));

apple爲參數,表達式裏面調用了apple,且表達式只要一個,可以轉化爲方法引用的形式

appleStore.forEach(System.out::println);

方法引用還有一種形式
當表達式爲前述參數只調用一次自己的方法,也可以變爲方法引用
參數類名::方法名

int redPriceSum = appleStore.stream()
                .filter(apple -> apple.getColor().equals("red"))
                .mapToInt(apple1->apple1.getPrice).sum();

可以看到mapToInt方法中的lambda表達式符合上述方法引用的形式,所以可以簡化爲

Apple::getPrice

其中Apple爲apple對象的類名

大致就是這些,想要了解更多使用的實例的可以去翻一翻集合的源碼,比如集合類的foreach方法,參數就是一個函數式接口,可以轉換爲lambda表達式,真的太寶藏啦!我現在才發現在Idea上面看源碼是多麼有意思,以後要常看常學!

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