做者:湯圓java
我的博客:javalover.cc設計模式
你們好啊,我是湯圓,今天給你們帶來的是《Java8中的Lambda表達式》,但願對你們有幫助,謝謝安全
文章純屬原創,我的總結不免有差錯,若是有,麻煩在評論區回覆或後臺私信,謝啦
Lambda表達式是一個可傳遞的代碼塊,能夠在之後執行一次或屢次;併發
下面貼個對比代碼:app
// Java8以前:舊的寫法 Runnable runnable = new Runnable() { @Override public void run() { System.out.println("old run"); } }; Thread t = new Thread(runnable); // Java8以後:新的寫法 Runnable runnable1 = ()->{ System.out.println("lambda run"); }; Thread t1 = new Thread(runnable1);
能夠看到,有了lambda,代碼變得簡潔多了ide
你能夠把lambda看成一個語法糖函數
下面讓咱們一塊兒來探索lambda的美好世界吧ui
下面列出本文的目錄this
下面分別說下語法中的三個組成部分spa
參數: ( Dog dog )
Comparator<String> comparatorTest = (a, b)->a.length()-b.length();
,能夠推導出a,b都爲String->
主體:{ System.out.println("javalover"); }
{;}
(好比上圖所示)a.length()- b.length()
)爲了簡化代碼
由於Java是面嚮對象語言,因此在lambda出現以前,咱們須要先構造一個對象,而後在對象的方法中實現具體的內容,再把構造的對象傳遞給某個對象或方法
可是有了lambda之後,咱們能夠直接將代碼塊傳遞給對象或方法
如今再回頭看下開頭的例子
能夠看到,用了lambda表達式後,少了不少模板代碼,只剩下一個代碼塊(最核心的部分)
就是隻定義了一個抽象方法的接口
@FunctionalInterface public interface FunctionInterfaceDemo { void abstractFun(); default void fun1(){ System.out.println("fun1"); } default void fun2(){ System.out.println("fun2"); } }
這裏的註解@FunctionalInterface能夠省略,可是建議加上,就是爲了告訴編譯器,這是一個函數式接口,此時若是該接口有多個抽象方法,那麼編譯器就會報錯
// 編譯器會報錯,Multiple non-overriding abstract methods found in XXX @FunctionalInterface public interface NoFunctionInterfaceDemo extends FunctionInterfaceDemo{ void abstractFun2(); }
上面的父接口FunctionInterfaceDemo中已經有了一個抽象方法,此時NoFunctionInterfaceDemo又定義了一個抽象方法,結果編譯器就提示了:存在多個抽象方法
在Java8以前,其實咱們已經接觸過函數式接口
好比Runnable 和 Comparable
只是沒有註解@FunctionalInterface。
那這個函數式接口要怎麼用呢?
配合lambda食用,效果最佳(就是把lambda傳遞給函數式接口),示例代碼以下:
new Thread(() -> System.out.println("run")).start();
其中用到的函數式接口是Runnable
就是把行爲定義成參數,行爲就是函數式接口
相似泛型中的類型參數化<T>
,類型參數化是把類型定義成參數
行爲參數化,通俗點來講:
這樣來看的話,行爲參數化和設計模式中的策略模式有點像了(後面章節會分別講經常使用的幾種設計模式)
下面咱們手寫一個函數式接口來加深理解吧
下面咱們按部就班,先從簡單的需求開始
public static String processFile() throws IOException { // Java7新增的語法,try(){},可自動關閉資源,減小了代碼的臃腫 try( BufferedReader bufferedReader = new BufferedReader(new FileReader("D:\\JavaProject\\JavaBasicDemo\\test.txt"))){ return bufferedReader.readLine(); } }
能夠看到,核心的行爲動做就是 return bufferedReader.readLine();
,表示讀取第一行的數據並返回
那若是咱們想要讀取兩行呢?三行?
@FunctionalInterface interface FileReadInterface{ // 這裏接受一個BufferedReader對象,返回一個String對象 String process(BufferedReader reader) throws IOException; }
能夠看到,只有一個抽象方法process()
,它就是用來處理第一步中的核心動做(讀取文件內容)
至於想讀取多少內容,那就須要咱們在lambda表達式中定義了
// 讀取一行 FileReadInterface fileReadInterface = reader -> reader.readLine(); // 讀取兩行 FileReadInterface fileReadInterface2 = reader -> reader.readLine() + reader.readLine();
processFile()
,讓其接受一個函數式接口,並調用其中的抽象方法,代碼以下:// 參數爲第二步咱們本身手寫的函數式接口 public static String processFile(FileReadInterface fileReadInterface) throws IOException { try( BufferedReader bufferedReader = new BufferedReader(new FileReader("./test.txt"))){ // 這裏咱們再也不本身定義行爲,而是交給函數式接口的抽象方法來處理,而後經過lambda表達式的傳入來實現多個行爲 return fileReadInterface.process(bufferedReader); } }
public class FileReaderDemo { public static void main(String[] args) throws IOException { // 第三步: // lambda表達式1 傳給 函數式接口:只讀取一行 FileReadInterface fileReadInterface = reader -> reader.readLine(); // lambda表達式2 傳給 函數式接口:只讀取兩行 FileReadInterface fileReadInterface2 = reader -> reader.readLine() + reader.readLine(); // 最後一步: 不一樣的函數式接口的實現,表現出不一樣的行爲 String str1 = processFile(fileReadInterface); String str2 = processFile(fileReadInterface2); System.out.println(str1); System.out.println(str2); } // 第四步: 讀取文件方法,接受函數式接口做爲參數 public static String processFile(FileReadInterface fileReadInterface) throws IOException { try( BufferedReader bufferedReader = new BufferedReader(new FileReader("./test.txt"))){ // 調用函數式接口中的抽象方法來處理數據 return fileReadInterface.process(bufferedReader); } } // 第一步: public static String processFile() throws IOException { try( BufferedReader bufferedReader = new BufferedReader(new FileReader("./test.txt"))){ return bufferReader.readLine(); } } } // 第二步: 咱們手寫的函數式接口 @FunctionalInterface interface FileReadInterface{ String process(BufferedReader reader) throws IOException; }
其實你會發現,咱們手寫的這個函數式接口,其實就是Function<T>
去除泛型化後的接口,以下所示:
@FunctionalInterface public interface Function<T, R> { // 都是接受一個參數,返回另外一個參數 R apply(T t); }
下面咱們列出Java中經常使用的一些函數式接口,你會發現自帶的已經夠用了,基本不會須要咱們本身去寫
這裏的手寫只是爲了本身實現一遍,能夠加深理解程度
咱們先看一個例子
前面咱們寫的lambda表達式,其實還能夠簡化,好比
// 簡化前 Function<Cat, Integer> function = c->c.getAge(); // 簡化後 Function<Cat, Integer> function2 = Cat::getAge;
其中簡化後的Cat::getAge
,咱們就叫作方法引用
方法引用就是引用類或對象的方法;
下面咱們列出方法引用的三種狀況:
像咱們上面舉的例子就是第三種:類的實例方法
下面咱們用代碼演示上面的三種方法:
public class ReferenceDemo { public static void main(String[] args) { // 第一種:引用對象的實例方法 Cat cat = new Cat(1); Function<Cat, Integer> methodRef1 = cat::getSum; // 第二種:引用類的靜態方法 Supplier<Integer> methodRef2 = Cat::getAverageAge; // 第三種:引用類的實例方法 Function<Cat, Integer> methodRef3 = Cat::getAge; } } class Cat { int age; public Cat(int age) { this.age = age; } // 獲取貓的平均年齡 public static int getAverageAge(){ return 15; } // 獲取兩隻貓的年齡總和 public int getSum(Cat cat){ return cat.getAge() + this.getAge(); } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
爲啥要用這個方法引用呢?
方法引用比如lambda表達式的語法糖,語法更加簡潔,清晰
一看就知道是調用哪一個類或對象的哪一個方法
上面介紹了方法引用,就是直接引用某個方法
這裏的構造引用同理可得,就是引用某個類的構造方法
構造引用的表達式爲:Class::new
,僅此一種
若是你有多個構造函數,那編譯器會本身進行推斷參數(你看看,多好,多簡潔)
好比下面的代碼:
// 這裏調用 new Cat() Supplier<Cat> constructRef1 = Cat::new; // 這裏調用 new Cat(Integer) Function<Integer, Cat> constructRef2 = Cat::new;
要求引入lambda表達式中的變量,必須是最終變量,即該變量不會再被修改
好比下面的代碼:
public static void main(String[] args) { String str = "javalover.cc"; Runnable runnable = ()->{ str = "1";// 這裏會報錯,由於修改了str引用的指向 System.out.println(str); } }
能夠看到,lambda表達式引用了外面的str引用,可是又在表達式內部作了修改,結果就報錯了
爲啥要有這個限制呢?
爲了線程安全,由於lambda表達式有一個好處就是隻在須要的時候纔會執行,而不是調用後立馬執行
這樣就會存在多個線程同時執行的併發問題
因此Java就從根源上解決:不讓變量被修改,都是隻讀的
那你可能好奇,我不把str的修改代碼放到表達式內部能夠嗎?
也不行,道理是同樣的,只要lambda有用到這個變量,那這個變量不論是在哪裏被修改,都是不容許的
否則的話,我這邊先執行了一次lambda表達式,結果你就改了變量值,那我第二次執行lambda,不就亂了嗎
最後是lambda的必殺技:組合操做
在這裏叫組合或者複合均可以
概述:組合操做就是先用一個lambda表達式,而後再在後面組合另外一個lambda表達式,而後再在後面組合另另外一個lambda表達式,而後。。。有點像是鏈式操做
學過JS的都知道Promise,裏面的鏈式操做就和這裏的組合操做很像
用過Lombok的朋友,應該很熟悉@Builder註解,其實就是構造者模式
下面咱們用代碼演示下組合操做:
// 重點代碼 public class ComposeDemo { public static void main(String[] args) { List<Dog> list = Arrays.asList(new Dog(1,2), new Dog(1, 1)); // 1. 先按年齡排序(默認遞增) // Dog::getAge, 上面介紹的方法引用 // comparingInt, 是Comparator的一個靜態方法,返回Comparator<T> Comparator<Dog> comparableAge = Comparator.comparingInt(Dog::getAge); // 2. 若是有相同的年齡,則年齡相同的再按體重排序(若是年齡已經比較出大小,則下面的體重就不會再去比較) Comparator<Dog> comparableWeight = Comparator.comparingInt(Dog::getWeight);; // 3. 調用list對象的sort方法排序,參數是Comparator<? super Dog> list.sort(comparableAge.thenComparing(comparableWeight)); System.out.println(list); } } // 非重點代碼 class Dog{ private int age; private int weight; public Dog(int age, int weight) { this.age = age; this.weight = weight; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public int getWeight() { return weight; } public void setWeight(int weight) { this.weight = weight; } @Override public String toString() { return "Dog{" + "age=" + age + ", weight=" + weight + '}'; } }
輸出:[Dog{age=1, weight=1}, Dog{age=1, weight=2}]
比較的流程以下所示:
(a,b)->{System.out.println("javalover.cc");}
方法引用:lambda的語法糖,總共有三種:
Class::new
好比list.sort(comparableAge.thenComparing(comparableWeight));
最後,感謝你們的觀看,謝謝