Lambda表達式 函數式編程 Stream API學習筆記

什麼是Lambda表達式

咱們知道接口是沒法直接被實例化的,你只能new他的實現類,可是假如當前接口只有一個抽象方法,又不想再建立一個類,在JDK8以前,你就只能使用匿名內部類。像下面這樣:html

new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello world");
            }
        }).start();

如今的IDE已經很強大了,你只用寫到new Runnable()就會幫你把代碼補全,可是你仍是要移動光標到run方法體內,寫對應的代碼。在學到匿名內部類的時候,我就但願代碼變的簡潔緊湊一些 (事實上官方寫的指導書《The Java™ Tutorials》也是將Lambda式放在匿名類這一章下面的,介紹Lambda表達式這一章在: https://docs.oracle.com/javas... )。
JDK8以後上面的代碼就能夠化簡成下面這樣:java

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

怎麼樣是否是變得至關簡潔了呢! ()-> System.out.println("hello world") 就能夠稱之爲Lambda表達式。Lambda是λ這個符號的名稱。
維基百科是這樣描述Lambda表達式的:sql

a function (or a subroutine) defined, and possibly called, without being bound to an identifier。

直接翻譯的話就是: 可能被調用的一個函數或者是子程序,可是沒有綁定到一個標識符上。數據庫

咱們放到java裏來理解一下,衆所周知java是面向對象的,函數也是放在一個類中的。調用方法的話就大體有如下兩種形式:express

  • 類名.方法名
  • 類的實例.方法名。

因此在java中,咱們姑且就能夠將類名+方法名稱之爲一個標識符,由於咱們經過類名(嚴謹的說是全類名)+方法名定位到一個方法。()-> System.out.println("hello world"),就是一個匿名類和匿名方法的組合化簡以後產物,類名沒有直接給出,方法名也是沒有,這些統統是編譯器來給。編程

百度百科是這樣描述Lambda表達式的:oracle

Lambda 表達式(lambda expression)是一個匿名函數,Lambda表達式基於數學中的λ演算得名,直接對應於其中的lambda抽象(lambda abstraction),是一個匿名函數,即沒有函數名的函數。--《百度百科》

咱們知道數學中的演算是須要運算符(演算符的),調用一個函數是須要給定參數的,是須要方法體的。app

() 裏面就能夠放參數,-> 是運算符,-> 以後就是方法體。形如()->{}的表達式,在java中咱們就能夠稱之爲Lambda表達式。
由於Runnable接口中的run方法沒有參數,因此()裏面啥也沒寫。

順便提一下,Lambda表達式並不算是java率先提出。2007年11月19,.NET Framework 3.5就已經引入了,jdk8的發佈時間是2014-03-18。主流的編程語言應該都有這個概念。編程語言

咱們來想一想爲何能夠化簡到這種地步,假如咱們有這樣兩個方法:ide

  • add(int i)
  • add(Long i)

咱們稱之爲方法重載,同一個方法名,參數類型不一樣。咱們在調用的時候,編譯器或者說是JVM,就能根據咱們給定的參數來調用對應的方法。那麼new Thread(()-> System.out.println("hello world"))來推斷這是Thread的哪個構造函數也是可以作到的事情。因此看似你寫的是這個:

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

事實上編譯器會幫你補成:

new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello world");
            }
        }).start();

這也是一些人說在某些狀況下Lambda性能不佳的緣由。

適用場景

若是你打開Runnable接口看的話,會發現Runnable接口上有一個註解: @FunctionalInterface。FunctionalInterface意味函數式接口,什麼是函數式接口呢? 就是一個接口中只有一個抽象方法的接口,咱們稱之爲函數式接口。咱們知道,jdk8以前接口中只能放抽象方法,jdk8以後能夠放非抽象方法。方法中要加上default關鍵字,咱們稱之爲默認實現。形式以下:

@FunctionalInterface
public interface MyLambdaInterface {
    void sayHello(String str);
    default void sayWorld(String str){
        System.out.println(str);
    }
}

加上@FunctionalInterface的接口,只能有一個抽象方法,容許存在和Object中方法名相同的方法,但要求參數類型一致。不然會通不過編譯。以下所示:

@FunctionalInterface
public interface MyLambdaInterface {
    void sayHello(String str);
    int hashCode();
    boolean equals(Object obj);
    default void sayWorld(String str){
        System.out.println(str);
    }
}

Lambda表達式的適用場景即爲: 假如我有一個函數式接口,可是呢,我又不想建立這個類的顯示實現類(顯示實現類即爲新建Class文件),我又想使用這個方法,Lambda表達式大顯身手的就到了。

如何使用以及 Lambda表達式的幾種形式

咱們首先介紹Lambda表達式的形式,在根據形式去講解如何使用。

  • 方法參數

Lambda從形式上來看更像是一個函數,(函數所須要的參數)->{方法實現}

方法參數的話 new Thread(()-> System.out.println("hello world")).start(); 這個就是一個例子。雖然原則上講並不該該直接建立一個線程,應該經過線程池。
咱們知道函數的參數類型能夠是基本類型和引用類型,new Thread(()-> System.out.println("hello world")) 這個例子好像就是將一個方法做爲參數傳遞進去了同樣。
  • 方法引用的形式一: 指向本身建立的方法

以MyLambdaInterface爲例,方法引用咱們能夠這麼寫:

MyLambdaInterface myLambdaInterface = (str)-> System.out.println(str);

從形式上來說,myLambdaInterface好像指向一個函數同樣。
(str)-> System.out.println(str);就是方法sayHello的實現。

  • 方法引用的形式二: 指向已存在的方法

指向已存在的方法的語法爲:

  • Reference to a static method(引用一個靜態方法):
public class Person {
    LocalDate birthday;
    public static int compareByAge(Person a, Person b) {
        return a.birthday.compareTo(b.birthday);
    }
}

方法引用的寫法: Person::compareByAge

可是這句話直接出如今代碼中會報錯,咱們須要找一個函數式接口來指向它。函數式接口中的方法返回類型和接收參數要和compareByAge保持一致,像下面這樣。

public interface CompareTo<T> {
    int compare(T t1,T t2);
}
CompareTo<Person> p = Person::compareByAge;
  • Reference to an instance method of an arbitrary object of a particular type(對特定類型的實例方法的調用)
String[] stringArray = {"Barbara", "James", "Mary", "John",
                "Patricia", "Robert", "Michael", "Linda"};
        Arrays.sort(stringArray, String::compareToIgnoreCase);
  • Reference to an instance method of a particular object (對特定對象的實例方法的調用)
    首先你須要new一個對象, 形式就是 函數式接口 = 對象::方法
    或者將 對象::方法當作一個方法參數傳遞進去。
  • Reference to a constructor (引用構造函數)

形式: 類名:: new。用法和前面三種是一致的,能夠當作一個函數式接口的實現,也能夠當作一個方法參數。

Lambda表達式本質上也是內部類,那麼同內部類同樣,要想要訪問局部變量,那麼這個局部變量必定要是final類型的。
如今咱們來總結一下Lambda表達式的寫法:

形式大體是這樣: (函數式接口所需的參數)->方法體;

假如方法體只有一行,能夠不用寫{
例如:

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

假若有多行的話須要寫{

咱們能夠認爲每個Lambda表達式都是一個函數式接口的匿名實現。

JDK提供的函數式接口

JDK也內置了一些函數式接口,這些函數式接口大多都在java.util.function下,理解這些函數式接口是學習Stream API的關鍵。這裏咱們選取幾個比較經典的講解一下。

  • Predicate 斷言中抽象方法爲: boolean test(T t);
  • Consumer 消費,抽象方法爲: void accept(T t);
  • Function<T, R> 函數式,抽象方法爲: R apply(T t);‘
  • Supplier<T> 供給型,抽象方法爲: T get();

boolean test(T t); 這個方法接收一個參數,返回一個boolean類型的值。

咱們能夠這樣寫:

Predicate<String> predicate = (str)-> str.contains("s");

還能夠這樣寫:

List<String> list = new ArrayList<>();
  Predicate<String> predicate = list::add;
  predicate.test("add");//等價於調用list.add("add")
  predicate.test("bb");

Consumer、 Function、 Supplier用法相似,這裏再也不作過多的介紹。

由函數式編程到 Stream API

首先Stream是一個接口,位於java.util.stream下。 假如咱們的集合泛型是一個學生類,像下面這樣。

List<Student> student = new ArrayList<>();

那麼假如我像統計年齡在18歲以上的學生總數,那麼在不用流的狀況下,咱們一般是這樣作的。

int count = 0;
    List<Student> studentList = new ArrayList<>();
    for (Student student : studentList) {
        if (student.getAge() > 18){
            count++;
        }
    }

Student能夠映射進數據庫中,咱們在數據庫作這樣的事情是簡單的,那麼在代碼可否像sql同樣執行count,where操做呢。
有了流以後就能夠,咱們能夠這樣寫:

studentStream.filter( (student) -> student.getAge() > 18).count();

是否是變的簡單多了呢! 那我能不能group by 在count呢,也是能夠的。按性別分組求出男生和女生的人數。

Map<Boolean, Long> result = studentStream.collect(Collectors.groupingBy(Student::isSex, counting()));

細究他的實現的話,這個並非本篇的主題,本篇只是簡單入門,詳細討論是另外一篇了。
順便提一下, 流有兩種類型,串行和並行流,這將是咱們下一篇文章重點闡釋的內容。

總結一下

  • Lambda表達式使得代碼更爲緊湊,使得能夠將方法做爲參數進行傳遞。
  • 由Lambda方法進一步到Stream,咱們就能夠將一些數據庫的操做,好比where,group by,移植進入代碼中,並且更爲簡潔。

參考資料: The Java™ Tutorials

相關文章
相關標籤/搜索