JDK8特性深刻學習筆記-Lambda表達式(1)

什麼是Lambda表達式

在編程語言中,lambda指的是一種運算符,用來表示:javascript

  • closures(閉包)
  • anonymous functions(匿名函數)

舊的語法經過傳遞數據來完成,而Lambda表達式更注重於行爲。在java中,Lambda表達式是個對象,它們必須依附於一類特別的對象類型 —— 函數式接口(functional interface)java

爲何須要Lambda表達式

  • 在java中,咱們沒法將函數做爲參數傳遞的方法,也沒法聲明返回一個函數的方法。
  • javascript中,函數參數是一個函數,返回值是另外一個函數的狀況是很是常見的。

Lamdba表達式的做用

  • 傳遞行爲,而不單單是值編程

    • 提高抽象層次
    • API重用性更好
    • 更加靈活

Lambda表達式的基本結構

(type1 arg1, type2 arg2, ..., typeN argN) -> { body }閉包

  • Lambda示例併發

    • (int a, int b) -> {return a + b;}
    • () -> System.out.println("Hello World");
    • (String s) -> {System.out.println(s);}
    • () -> 42
    • () -> {return 4};
  • 一個Lambda表達式能夠有零個或多個參數
  • 參數類型能夠明確聲明,也可讓編譯器經過上下文推斷。例如:(int a) 與 (a) 效果相同
  • 全部參數須要包含在圓括號內,參數之間用逗號相隔。例如:(a, b)或(int a, int b)或 (String a, int b, double c)
  • 圓空括號表示參數集合爲空。例如() -> 42
  • 當只有一個參數,且其類型能夠推斷時,圓括號()能夠省略。例如: a -> return a*a
  • Lambda表達式的主題可包含零條或多條語句
  • 若是Lambda表達式的主題只有一條語句,花括號{}能夠省略。匿名函數的返回類型與該主體表達式一致。
  • 若是Lambda表達式的主題包含一條以上的語句,則表達式必須包含在花括號{}中(造成代碼塊)。匿名函數的返回類型與代碼塊的返回類型一致,若沒有返回則爲空。

函數式接口

經過Lambda表達式遍歷集合

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);

list.forEach(new Consumer<Integer>(){
    @Override
    public void accept(Integer integer){
        System.out.println(integer);
    }
});

Consumer接口

@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); };
    }
}

關於函數式接口 註解

@FunctionalInterface:凡是接口上帶上這個註解,即爲函數式接口。一個函數式接口有且只有一個精確的抽象方法。函數式接口的實例能夠經過Lamdba表達式的方法引用構造方法引用來去建立。
若是使用了@FunctionalInterface註解,但卻不知足如下兩個條件,編譯器會返回編譯異常:app

  • 這個類型必須是Interface接口類型
  • 這個註解類型必須知足函數式接口的要求:一個函數式接口有且只有一個精確的抽象方法

若是這個抽象方法複寫了或者重寫了Object類裏的public方法,那麼這個接口也不屬於函數式接口。由於java的任意實現,必定都會有一個來自java.lang.Object的實現。編程語言

@FunctionalInterface
public interface MyInterface{
    // 抽象方法
    void test();
    // Object的抽象方法
    String toString();
}

toString()是Object的抽象方法,因此編譯器認爲MyInterface依然是一個函數式接口。ide

public class UsingFunctionInterface{
    public void myUsingInterface(MyInterface interface){
        System.out.println("start");
        interface.test();
        System.out.println("end");
    }

    public static void main(String[] args){
        UsingFunctionInterface u = new UsingFunctionInterface();

        // 寫法1
        u.myUsingInterface(() -> {
             System.out.println("test print");
        });

        //寫法2
        MyInterface myInterface = () -> {
             System.out.println("test print");
        };

        u.myUsingInterface(myInterface);
    }
}

Lamdba表達式弱化了抽象方法名的意義,將方法實現(即->右邊的部分,直接做爲函數式接口的實現)。函數

關於函數式接口的特色ui

  • 若是一個接口只有一個抽象方法,那麼這個接口就是函數式接口
  • 若是咱們在某個接口上聲明瞭@FunctionalInterface,那麼編譯器就會按照函數式接口的定義來要求該接口
  • 若是某個接口只有一個抽象方法,但咱們並無給該接口聲明@FunctionalInterface,那麼編譯器依舊會將該接口看做是函數式接口。
  • 函數式接口的實例,能夠經過Lamdba表達式方法的實例構造方法的實例來建立。

上述的總結:@FunctionalInterface只是幫助編譯器檢查函數式接口是否合法。

若是一個接口須要用做函數式接口,那麼最好加上 @FunctionalInterface

foreach方法

list.foreach()

foreach的實現

default void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);
    for (T t : this) {
        action.accept(t);
    }
}

foreach最特殊的就是用了default關鍵字。jdk1.8後,接口裏也能夠有方法實現。一旦在接口中使用了default關鍵字作方法的實現,稱之爲默認方法(default Method)
默認方法的出現一方面保證了jdk8的新特性的加入,另外一方面保證了jdk8能夠向上兼容。

Consumer

Consumer是一個操做,這個操做接受一個單個的輸入元素,而且不返回結果。與其餘函數式接口不一樣的是,它可能會修改接收到的操做
Consumer的語義是消費者,因此顧名思義,這個類中的抽象方法void accept(T t)的語義爲:接受一個類,並將其消費掉,並不關心結果

迭代

外部迭代:指的是利用外部的迭代器,對一個集合進行迭代。
內部迭代:經過集合自己,利用Lambda表達式進行迭代。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
// 外部迭代
for(Integer i : numbers){
    System.out.println(i);
}
// 內部迭代 : 函數式接口
numbers.forEach(new Consumer<Integer>{
    public void accept(){
        System.out.println(i);
    }
});
// 內部迭代: 函數式接口 + Lambda表達式
numbers.forEach(i -> System.out.println(i) )
// 內部迭代: 方法引用
numbers.forEach(System.out::println)

使用stream作迭代

stream的實現

default Stream<E> stream() {
        return StreamSupport.stream(spliterator(), false);
    }

stream是在Collection接口的默認方法,返回一個串行流。
spliterator(分割迭代器)方法不能返回一個分割迭代器時(不可變的、延遲的、併發的分割迭代器),stream方法須要被重寫。

stream中的map()

<R> Stream<R> map(Function<? super T, ? extends R> mapper); ,map自己的含義是映射,將一個給定的元素映射爲另外一個元素並返回一個新的Stream

List<String> list = Arrays.asList("a", "b", "c");
List<String> list2 = new ArrayList<>();
// Lamdba表達式實現
list.stream().map(i -> i.toUpperCase()).forEach(i -> list2.add(i));
// 方法引用實現
list.stream().map(String::toUpperCase).forEach(list2::add);
相關文章
相關標籤/搜索