知識不回顧是會被遺忘的!
網上看了一些相關文章,這里記錄一下,僅供參考
Java語言從JDK1.8開始引入了函數式編程。
????????函數式編程的核心特點是,函數作為一段功能代碼,可以像變量一樣進行引用和傳遞,以便在有需要的時候進行調用。
1. @FunctionalInterface與“函數類型”
????????Java對函數式編程的支持,本質是通過接口機制來實現的。首先定義一個僅聲明一個方法的接口,然后對接口冠以@FunctionalInterface注解,那么這個接口就可以作為“函數類型”,可以接收一段以Lambda表達式,或者方法引用予以承載的邏輯代碼。例如:
@FunctionalInterface
interface IntAdder {int add(int x, int y);
}IntAdder adder = (x, y) -> x + y;
IntAdder 就可以看成是一個“函數類型”。Lambda表達式和方法引用的介紹見后文。
概念如此,需要思考的有幾點:
為什么必須是只聲明一個方法的接口?
????????顯然這個方法就是用來代表“函數類型”所能執行的功能,一個函數一旦定義好,它能執行的功能是確定的,就是調用和不調用的區別。接口中聲明的方法就是和函數體定義一一對應的。
????????事實上,@FunctionalInterface下只能聲明一個方法,多一個、少一個都不能編譯通過 。覆寫Object中toString/equals的方法不受此個數限制。
比如Comparator接口就聲明了2個方法:
// Comparator.java
@FunctionalInterface
public interface Comparator<T> {int compare(T o1, T o2);boolean equals(Object obj);//...
}
????????嚴格地說,@FunctionalInterface下只能聲明一個未實現的方法,default方法和static方法因為帶有實現體,所有不受此限制。
?@FunctionalInterfacepublic interface IAdd<T, R> {R add(T t1, T t2);default R test1(T t1, T t2) {//可以額外定義default方法return null;}static <T,R> R test2(T t1, T t2) {//可以額外定義static方法return null;}}
? ? ? ? 關于interface中聲明default/static方法有疑慮的話,可以查閱博主另一篇文章:java接口里面可以有成員變量么?
????????@FunctionalInterface注解不是必須的,不加這個注解的接口(前提是只包含一個方法)一樣可以作為函數類型。不過,顯而易見的是,加了這個注解表意更明確、更直觀,是更被推薦的做法。
????????要定義清楚一個函數類型,除了函數名稱,必須明確規定函數的參數個數和類型、返回值類型,這些信息都是包含于接口中聲明的方法。
2. JDK提供的“函數類型”
java.util.function包下預定義了常用的函數類型,包括:
@FunctionalInterface
public interface Consumer<T> {void accept(T t); //接收一個類型為T(泛型)的參數,無返回值;所以叫消費者
}
@FunctionalInterface
public interface BiConsumer<T, U> {void accept(T t, U u);//接收2個參數,無返回值
}
@FunctionalInterface
public interface Supplier<T> {T get();//無參數,有返回值(所以叫提供者)
}
//注意沒有BiSupplier,因為返回值只能有1個,不會有2個
@FunctionalInterface
public interface Function<T, R> {R apply(T t);//一個輸入(參數),一個輸出(返回值)
}
@FunctionalInterface
public interface BiFunction<T, U, R> {R apply(T t, U u);//兩個輸入T和U,一個輸出R
}
@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {static <T> UnaryOperator<T> identity() {//一元操作,輸入原樣返回給輸出return t -> t;}
}
@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T,T,T> {//二元操作,輸入輸出類型相同public static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator) {Objects.requireNonNull(comparator);return (a, b) -> comparator.compare(a, b) <= 0 ? a : b;//傳入比較器,返回較小者}public static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator) {Objects.requireNonNull(comparator);return (a, b) -> comparator.compare(a, b) >= 0 ? a : b;//傳入比較器,返回較大者}
}
? ? ? ? 這些個定義,都是在參數個數(0,1,2)和有無返回值上做文章。另外還有一些將泛型類型具體化的衍生接口,比如Predicate、LongSupplier等等。
@FunctionalInterface
public interface Predicate<T> {boolean test(T t);//輸入1個參數,返回boolean,就好比是預言家,預言你這個輸入是真還是假
}
@FunctionalInterface
public interface LongSupplier {long getAsLong();//沒有輸入,輸出long類型(long類型的提供者)
}
3. Lambda表達式
????????上面弄清楚了函數類型@FunctionalInterface,那么函數類型能接收怎么樣的函數實現體呢?怎么接收呢?該Lambda出場了。
????????Lambda表達式能賦值給一個變量,也就能當作參數傳給函數。這個Lambda形式的變量/參數的類型是它所實現的那個接口,所包含的方法體便是這個接口抽象方法的實現。以后看到調用方法的參數是一個SAM類型接口的時候就可以考慮使用Lambda表達式替換匿名內部類來寫。
作用:
- ①減少代碼量,突出代碼意圖
- ②對集合數據 Collection 操作更簡便
- ③使變量記住一段邏輯:
? ? ? ? 任務邏輯傳遞(傳遞一段運算邏輯給執行者)
? ? ? ? 回調邏輯傳遞(簡化接口回調的時候 new匿名類后實現抽象方法的模版代碼)
將一個方法寫成Lambda表達式,只需要關注參數列表和方法體。????????
語法組成:
(參數類型 參數名) -> {
? ? ? ? 方法體;
? ? ? ? return 返回值;
}
- ①形式參數:最前面的部分是一對括號,里面是參數,無參數就是一對空括號
- ②箭頭:中間的是 -> ,用來分割參數和body部分,讀作“ goes to”
- ③方法體:body部分可以是一個表達式或者一個代碼塊。
簡寫:
- ①可選類型聲明:不用聲明參數類型,編譯器可以自動識別。
- ②可選參數括號:一個參數無需定義括號,多個參數需要定義。
- ③可選大括號:如果body部分只包含一個語句或表達式,就不需要使用大括號括起來。
- ④可選 return 關鍵字:如果body部分是一個表達式,表達式的值會被作為返回值返回;如果是代碼塊,需要用 return 指定返回值
????????Java8 之前創建接口實現類總會有很多冗余的模版代碼,接口中定義的抽象方法越多,每次實現的模版代碼就越多,而很多時候這個接口實現類只需要用到一次。?
變量作用域 :
- ①局部變量:引用的局部變量不可被修改值,即必須是final所修飾的,或者不被后面代碼更改的(隱形final屬性)。
- ②成員變量:可以修改類成員變量(非 final 修飾的)和靜態變量。
- ③在Lambda表達式中,不允許聲明一個與局部變量同名的參數或者局部變量。
- ④在Lambda表達式中,使用this調用的是該Lambda表達式所屬方法的所屬實例的this。
- ⑤在Lambda表達式中,無法訪問到自身接口的默認方法(不存在實例)。
????????Lambda表達式用來定義函數實現體。有很多種寫法(都是為了簡化書寫),但核心是通過->連接參數和實現代碼:
(入參)->{實現代碼}
//無返回值的時候
(int x)->{System.out.println(x);}
(x)->{System.out.println(x);}//參數類型自動推斷
x->{System.out.println(x);}//只有一個參數的時候,可以省略小括號
x->System.out.println(x);//實現體只有一個表達式可以省略大括號,System.out.println本身無返回值
//有返回值的情況
(int x)->{return x*x;}
(x)->{return x*x;}
//x->return x*x; //錯誤,不能這么寫!!
x->x*x;
說了這么多,來實操一把:
IntConsumer ic = x->System.out.println(x);
IntFunction<Integer> ifi1 = x->{return x*x;};
IntFunction<Integer> ifi2 = x->x*x;
ic.accept(100);//100
System.out.println(ifi1.apply(5));//25
System.out.println(ifi2.apply(5));//25
好了,函數類型–>Lambda表達式說明白了,再來看看方法引用是怎么回事。
4. 四種方法引用
????????文章開頭說過了,函數類型可以接收一段Lambda表達式,或者對方法的引用。方法引用就是對一個類中已經存在的方法加以引用,分4中類型:(以Test類為例)
- 對類構造方法的引用,如Test::new。
- 對類靜態方法的引用,如Test::staticMethodName
- 對對象實例方法的引用,如:new Test()::instanceMethod
- 是2和3的結合,如Test::instanceMethod2,但要求函數類型聲明和函數調用的時候,其第一個參數必須是Test類的實例。
第4種比較難以說清楚,看看下面的例子吧:
public class Test {private String name = "";public Test() {System.out.println("構造方法:無參數");}public Test(String name) {this.name = name;System.out.println("構造方法:參數="+name);}public static void staticMethod(String str) {System.out.println("static method: input=" + str);}public void instanceMethod(String str) {System.out.println("instance method: input=" + str);}public static void main(String[] args) {Supplier<Test> s1 = Test::new;//對無參構造器的引用,無參構造器其實就是一個對象的Supplier(提供者)s1.get();//調用構造方法:無參數Function<String, Test> f1 = Test::new;//引用有一個String參數的構造器f1.apply("Test");//調用構造方法:參數=TestConsumer<String> c1 = Test::staticMethod;//對靜態方法引用c1.accept("1");//static method: input=1Consumer<String> c2 = new Test()::instanceMethod;//對實例方法的引用c2.accept("2");//instance method: input=2//第4種BiConsumer<Test, String> bc1 = Test::instanceMethod;bc1.accept(new Test(), "3");//instance method: input=3}
}?
第4中方法引用,本質上是對實例方法的引用,只不過是在調用的時候才傳入那個實例對象。
5. andThen鏈式表達
JDK中很多函數類型,都實現了default的andThen方法,可以將多個函數體(Lambda表達式、方法引用)串起來,方便進行鏈式調用。
調用鏈上的任何一個拋出異常,整個調用鏈會提前結束,異常由調用者處理。
/*** 通過andThen()進行鏈式操作*/
@Test
public void testLinkConsumer() {IntConsumer action = x-> System.out.print(x);action = action.andThen(x->System.out.print("--tail1")).andThen(x->System.out.print("--tail2"));//100--tail1--tail2action.accept(100);
}
6. 最后
小插曲:Callable和Runnable到底什么區別?
//java.util.concurrent.Callable
@FunctionalInterface
public interface Callable<V> {V call() throws Exception;
}
//java.lang.Runnable
@FunctionalInterface
public interface Runnable {public abstract void run();
}
這兩者也都是JDK預定義的函數接口,兩者都不接收參數,主要用于多線程編程。
Runnable無返回值,一般用于new一個新線程的時候,在新線程中執行代碼。
Callable一樣一般用于在新線程中執行,只不過執行成功后有返回值,如果執行失敗還會拋異常。
最后,一起分析:
Callable<Integer> c1 = ()->1;
Callable<Integer> c2 = ()->c1.call();
c1引用了一個Lambda表達式;
c2引用了一個新的Lambda表達式,表示式的實現代碼中調用了c1提供的call()方法,并將call()方法的返回值返回。
Callable<Integer> c1 = ()->1;
Callable<Integer> c2 = ()->c1.call();
Callable<Integer> c3 = ()->{System.out.println("c3 call c1");return c1.call();
};try {System.out.println(c1.call());//1System.out.println(c2.call());//1System.out.println(c3.call());//c3 call c1//1
} catch (Exception e) {e.printStackTrace();
}
注意,c3和c2本質上是一樣的,只不過方法實現上,多加了一行打印代碼。
7、Lambda 表達式的各種形態和使用場景
????????Lambda 表達式是 Java 8 中添加的功能。引入 Lambda 表達式的主要目的是為了讓 Java 支持函數式編程。 Lambda 表達式是一個可以在不屬于任何類的情況下創建的函數,并且可以像對象一樣被傳遞和執行。
Java lambda 表達式用于實現簡單的單方法接口,與 Java Streams API 配合進行函數式編程。
????????在前幾篇關于 List、Set 和 Map 的文章中,我們已經看到了這幾個 Java 容器很多操作都是通過 Stream 完成的,比如過濾出對象 List 中符合條件的子集時,會使用類似下面的 Stream 操作。
List<A> list = aList.filter(a -> a.getId() > 10).collect(Colletors.toList);
????????其中filter
方法里用到的a -> a.getId() > 10
就是一個 Lambda 表達式,前面對用到 Lambda 的地方知識簡單的說了一下,如果你對各種 Stream 操作有疑問,可以先把本篇 Lambda 相關的內容學完,接下來再仔細梳理 Stream 時就會好理解很多了。
Lambda 表達式和函數式接口
????????上面說了 lambda 表達式便于實現只擁有單一方法的接口,同樣在 Java 里匿名類也用于快速實現接口,只不過 lambda 相較于匿名類更方便些,在書寫的時候連創建類的步驟也免去了,更適合用在函數式編程。
????????舉個例子來說,函數式編程經常用在實現事件 Listener 的時候 。 在 Java 中的事件偵聽器通常被定義為具有單個方法的 Java 接口。下面是一個 Listener 接口示例:
public interface StateChangeListener {public void onStateChange(State oldState, State newState);
}
????????上面這個 Java 接口定義了一個只要被監聽對象的狀態發生變化,就會調用的 onStateChange 方法(這里不用管監聽的是什么,舉例而已)。 在 Java 8 版本以前,監聽事件變更的程序必須實現此接口才能偵聽狀態更改。
比如說,有一個名為 StateOwner 的類,它可以注冊狀態的事件偵聽器。
public class StateOwner {public void addStateListener(StateChangeListener listener) { ... }
}
我們可以使用匿名類實現 StateChangeListener 接口,然后為 StateOwner 實例添加偵聽器。
StateOwner stateOwner = new StateOwner();stateOwner.addStateListener(new StateChangeListener() {public void onStateChange(State oldState, State newState) {// do something with the old and new state.System.out.println("State changed")}
});
????????在 Java 8 引入Lambda 表達式后,我們可以用 Lambda 表達式實現 StateChangeListener 接口會更加方便。
????????現在,把上面例子接口的匿名類實現改為 Lambda 實現,程序會變成這樣:
StateOwner stateOwner = new StateOwner();stateOwner.addStateListener((oldState, newState) -> System.out.println("State changed")
);
在這里,我們使用的 Lambda 表達式是:
(oldState, newState) -> System.out.println("State changed")
????????這個 lambda 表達式與 StateChangeListener 接口的 onStateChange() 方法的參數列表和返回值類型相匹配。如果一個 lambda 表達式匹配單方法接口中方法的參數列表和返回值(比如本例中的 StateChangeListener 接口的 onStateChange 方法),則 lambda 表達式將轉換為擁有相同方法簽名的接口實現。?這句話聽著有點繞,下面詳細解釋一下 Lambda 表達式和接口匹配的詳細規則。
匹配Lambda 與接口的規則
????????上面例子里使用的 StateChangeListener 接口有一個特點,其只有一個未實現的抽象方法,在 Java 里這樣的接口也叫做函數式接口 (Functional Interface)。將 Java lambda 表達式與接口匹配需要滿足一下三個規則:
- 接口是否只有一個抽象(未實現)方法,即是一個函數式接口?
- lambda 表達式的參數是否與抽象方法的參數匹配?
- lambda 表達式的返回類型是否與單個方法的返回類型匹配?
如果能滿足這三個條件,那么給定的 lambda 表達式就能與接口成功匹配類型。
函數式接口
????????只有一個抽象方法的接口被稱為函數是式接口,從 Java 8 開始,Java 接口中可以包含默認方法和靜態方法。默認方法和靜態方法都有直接在接口聲明中定義的實現。這意味著,Java lambda 表達式可以實現擁有多個方法的接口——只要接口中只有一個未實現的抽象方法就行。
????????所以在文章一開頭我說lambda 用于實現單方法接口,是為了讓大家更好的理解,真實的情況是只要接口中只存在一個抽象方法,那么這個接口就能用 lambda 實現。
????????換句話說,即使接口包含默認方法和靜態方法,只要接口只包含一個未實現的抽象方法,它就是函數式接口。比如下面這個接口:
import java.io.IOException;
import java.io.OutputStream;public interface MyInterface {void printIt(String text);default public void printUtf8To(String text, OutputStream outputStream){try {outputStream.write(text.getBytes("UTF-8"));} catch (IOException e) {throw new RuntimeException("Error writing String as UTF-8 to OutputStream", e);}}static void printItToSystemOut(String text){System.out.println(text);}
}
即使這個接口包含 3 個方法,它也可以通過 lambda 表達式實現,因為接口中只有一個抽象方法 printIt沒有被實現。
MyInterface myInterface = (String text) -> {System.out.print(text);
};
Lambda VS 匿名類
????????盡管 lambda 表達式和匿名類看起來差不多,但還是有一些值得注意的差異。 主要區別在于,匿名類可以有自己的內部狀態--即成員變量,而 lambda 表達式則不能。
public interface MyEventConsumer {public void consume(Object event);
}
比如上面這個接口,通過匿名類實現
MyEventConsumer consumer = new MyEventConsumer() {public void consume(Object event){System.out.println(event.toString() + " consumed");}
};
MyEventConsumer 接口的匿名類可以有自己的內部狀態。
MyEventConsumer myEventConsumer = new MyEventConsumer() {private int eventCount = 0;public void consume(Object event) {System.out.println(event.toString() + " consumed " + this.eventCount++ + " times.");}
};
????????我們給匿名類,加了一個名為 eventCount 的整型成員變量,用來記錄匿名類 consume 方法被執行的次數。Lambda 表達式則不能像匿名類一樣添加成員變量,所以也成 Lambda 表達式是無狀態的。
推斷 Lamdba 的接口類型
????????使用匿名類實現函數式接口的時候,必須在 new 關鍵字后指明實現的是哪個接口。比如上面使用過的匿名類例子
stateOwner.addStateListener(new StateChangeListener() {public void onStateChange(State oldState, State newState) {// do something with the old and new state.}
});
????????但是 lambda 表達式,通常可以從上下文中推斷出類型。例如,可以從 addStateListener() 方法聲明中參數的類型 StateChangeListener 推斷出來,Lambda 表達式要實現的是 StateChangeListener 接口。
stateOwner.addStateListener((oldState, newState) -> System.out.println("State changed")
);
????????通常 lambda 表達式參數的類型也可以推斷出來。在上面的示例中,編譯器可以從StateChangeListener 接口的抽象方法 onStateChange() 的方法聲明中推斷出參數 oldState 和 newState 的類型。
Lambda 的參數形式
????????由于 lambda 表達式實際上只是個方法,因此 lambda 表達式可以像方法一樣接受參數。Lambda 表達式參數根據參數數量以及是否需要添加類型會有下面幾個形式。
如果表達式的方法不帶參數,那么可以像下面這樣編寫 Lambda 表達式:
() -> System.out.println("Zero parameter lambda");
如果表達式的方法接受一個參數,則可以像下面這樣編寫 Lambda 表達式:
(param) -> System.out.println("One parameter: " + param);
當 Lambda 表達式只接收單個參數時,參數列表外的小括號也可以省略掉。
param -> System.out.println("One parameter: " + param);
????????當 Lambda 表達式接收多個參數時,參數列表的括號就沒法省略了。
????????如果編譯器無法從 Lambda 匹配的函數式接口的方法聲明推斷出參數類型(出現這種情況時,編譯器會提示),則有時可能需要為 Lambda 表達式的參數指定類型。
(Car car) -> System.out.println("The car is: " + car.getName());
Lambda 的方法體
lambda 表達式的方法的方法體,在 Lambda 聲明中的 -> 右側指定:
(oldState, newState) -> System.out.println("State changed")
如果 Lambda 表達式的方法體需要由多行組成,則需要把多行代碼寫在用{ }括起來的代碼塊內。
(oldState, newState) -> {System.out.println("Old state: " + oldState);System.out.println("New state: " + newState);
}
Lamdba 表達式的返回值
????????可以從 Lambda 表達式返回值,就像從方法中返回值一樣。只需在 Lambda 的方法體中添加一個 return 語句即可:
(param) -> {System.out.println("param: " + param);return "return value";
}
????????如果 Lambda 表達式所做的只是計算返回值并返回它,我們甚至可以省略 return 語句。
(a1, a2) -> { return a1 > a2; }
// 上面的可以簡寫成,不需要return 語句的
(a1, a2) -> { a1 > a2; }
????????Lambda 表達式本質上是一個對象,跟其他任何我們使用過的對象一樣, 我們可以將 Lambda 表達式賦值給變量并進行傳遞和使用。
public interface MyComparator {public boolean compare(int a1, int a2);}---MyComparator myComparator = (a1, a2) -> a1 > a2;boolean result = myComparator.compare(2, 5);
????????上面的這個例子展示 Lambda 表達式的定義,以及如何將 Lambda 表達式賦值給給變量,最后通過調用它實現的接口方法來調用 Lambda 表達式。
外部變量在 Lambda 內的可見性
????????在某些情況下,Lambda 表達式能夠訪問在 Lambda 函數體之外聲明的變量。 Lambda 可以訪問以下類型的變量:
- 局部變量
- 實例變量
- 靜態變量
Lambda 內訪問局部變量,Lambda 可以訪問在 Lambda 方法體之外聲明的局部變量的值
public interface MyFactory {public String create(char[] chars);
}String myString = "Test";MyFactory myFactory = (chars) -> {return myString + ":" + new String(chars);
};
Lambda 訪問實例變量,Lambda 表達式還可以訪問創建了 Lambda 的對象中的實例變量。
public class EventConsumerImpl {private String name = "MyConsumer";public void attach(MyEventProducer eventProducer){eventProducer.listen(e -> {System.out.println(this.name);});}
}
????????這里實際上也是 Lambda 與匿名類的差別之一。匿名類因為可以有自己的實例變量,這些變量通過 this 引用來引用。但是,Lambda 不能有自己的實例變量,因此 this 始終指向外面包裹 Lambda 的對象。
Lambda 訪問靜態變量,Lambda 表達式也可以訪問靜態變量。這也不奇怪,因為靜態變量可以從 Java 應用程序中的任何地方訪問,只要靜態變量是公共的。
public class EventConsumerImpl {private static String someStaticVar = "Some text";public void attach(MyEventProducer eventProducer){eventProducer.listen(e -> {System.out.println(someStaticVar);});}
}
把方法引用作為 Lambda
????????如過編寫的 lambda 表達式所做的只是使用傳遞給 Lambda 的參數調用另一個方法,那么 Java里為 Lambda 實現提供了一種更簡短的形式來表達方法調用。比如說,下面是一個函數式數接口:
public interface MyPrinter{public void print(String s);
}
接下來我們用 Lambda 表達式實現這個 MyPrinter 接口
MyPrinter myPrinter = (s) -> { System.out.println(s); };
因為 Lambda 的參數只有一個,方法體也只包含一行,所以可以簡寫成
MyPrinter myPrinter = s -> System.out.println(s);
????????又因為 Lambda 方法體內所做的只是將字符串參數轉發給 System.out.println() 方法,因此我們可以將上面的 Lambda 聲明替換為方法引用。
MyPrinter myPrinter = System.out::println;
注意雙冒號 :: 向 Java 的編譯器指明這是一個方法的引用。引用的方法是雙冒號之后的方法。而擁有引用方法的類或對象則位于雙冒號之前。
我們可以引用以下類型的方法:
- 靜態方法
- 參數對象的實例方法
- 實例方法
- 類的構造方法
引用類的靜態方法
最容易引用的方法是靜態方法,比如有這么一個函數式接口和類
public interface Finder {public int find(String s1, String s2);
}public class MyClass{public static int doFind(String s1, String s2){return s1.lastIndexOf(s2);}
}
如果我們創建 Lambda 去調用 MyClass 的靜態方法 doFind
Finder finder = (s1, s2) -> MyClass.doFind(s1, s2);
所以我們可以使用 Lambda 直接引用 Myclass 的 doFind 方法。
Finder finder = MyClass::doFind;
引用參數的方法
接下來,如果我們在 Lambda 直接轉發調用的方法是來自參數的方法
public interface Finder {public int find(String s1, String s2);
}Finder finder = (s1, s2) -> s1.indexOf(s2);
依然可以通過 Lambda 直接引用
Finder finder = String::indexOf;
????????這個與上面完全形態的 Lambda 在功能上完全一樣,不過要注意簡版 Lambda 是如何引用單個方法的。 Java 編譯器會嘗試將引用的方法與第一個參數的類型匹配,使用第二個參數類型作為引用方法的參數。
引用實例方法
我們還也可以從 Lambda 定義中引用實例方法。首先,設想有如下接口
public interface Deserializer {public int deserialize(String v1);
}
該接口表示一個能夠將字符串“反序列化”為 int 的組件。現在有一個 StringConvert 類
public class StringConverter {public int convertToInt(String v1){return Integer.valueOf(v1);}
}
????????StringConvert 類 的 convertToInt() 方法與 Deserializer 接口的 deserialize() 方法具有相同的簽名。因此,我們可以創建 StringConverter 的實例并從 Lambda 表達式中引用其 convertToInt() 方法,如下所示:
StringConverter stringConverter = new StringConverter();Deserializer des = stringConverter::convertToInt;
// 等同于 Deserializer des = (value) -> stringConverter.convertToInt(value)
????????上面第二行代碼創建的 Lambda 表達式引用了在第一行創建的 StringConverter 實例的 convertToInt 方法。
引用構造方法
????????最后如果 Lambda 的作用是調用一個類的構造方法,那么可以通過 Lambda 直接引用類的構造方法。在 Lambda 引用類構造方法的形式如下:
ClassName::new
那么如何將構造方法用作 lambda 表達式呢,假設我們有這樣一個函數式接口
public interface Factory {public String create(char[] val);
}
Factory 接口的 create() 方法與 String 類中的其中一個構造方法的簽名相匹配(String 類有多個重載版本的構造方法)。因此,String類的該構造方法也可以用作 Lambda 表達式。
Factory factory = String::new;
// 等同于 Factory factory (chars) -> String.new(chars);
總結
????????今天這篇文章把 Lambda 表達式的知識梳理的了一遍,相信看完了這里的內容,再看到 Lambda 表達式的各種形態就不覺得迷惑了,雖然今天的文章看起來有點枯燥,不過是接下來 咱們系統學習 Stream 操作的基礎,以及后面介紹 Java 中提供的幾個函數式編程 interface 也會用到 Lambda 里的知識,后面的內容可以繼續期待一下。
參考:
Java 8 (2/6篇) - Lambda表達式 & 函數式接口(FunctionalInterface Lib)_java8 lambda函數式接口_Jomurphys的博客-CSDN博客
徹底弄懂@FunctionalInterface、Lambda表達式和方法引用_@interface 表達式-CSDN博客
Java Lambda 表達式的各種形態和使用場景,看這篇就夠了 - 知乎