作者jtorngl (DDFL)
看板java
標題[問題] method reference
時間Wed Aug 21 14:34:30 2019
最近才開始看 lambda expression
直接看程式碼都覺得很難看懂
有種必須以 compiler 的角度來看才知道
特別是 lambda 只要符合 function descriptor 都可編譯
但是 target type 會是什麼,第一時間還真不知道是什麼
然後在練習 method reference 時,有一個地方一直無法理解
請看下面程式碼部份的中文
另問,如果要問問題,有什麼比較好貼出程式碼的地方嗎?
js 常看到 js fiddle
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
public class MethodReference {
public static void main(String[] args) {
List<String> strList = Arrays.asList("a", "b", "x");
/* == anonymous class == */
System.out.println("== anonymous class ==");
strList.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.print(s + ",");
}
}); // a,b,x,
/*
* == lambda expression ==
* Consumer<String> abstract method: void accept(String s);
* function descriptor: str -> void
*/
System.out.println("\n== lambda expression ==");
strList.forEach( str -> System.out.print(str + ",") );
strList.forEach( str -> {} );
strList.forEach( str -> {return;} );
/* Error: incompatible types: bad return type in lambda expression */
// strList.forEach( str -> "[" + str + "]" );
這裡使用 lambda 時,回傳值非 void 時會編譯錯誤
我是用,因為 forEach() 要的是 Consumer
所以要符合 Consumer 的 function descriptor 去理解
/* == Class::staticMethod == */
System.out.println("\n== Class::staticMethod ==");
strList.forEach(MethodReference::staticPrint);
strList.forEach(MethodReference::xxx);
/* compile success?? */
strList.forEach(MethodReference::yyy);
這裡編譯也成功,參數數目對了,但是方法回傳值不為 void
那 lambda 不行,但是 method reference 卻可以
是什麼原因呢?
因為 method 的 signature 不包括 return type ?
/* Error: incompatible types: invalid method reference */
// strList.forEach(MethodReference::zzz);
// strList.forEach(MethodReference::www);
這裡也會編譯錯誤,因為 Consumer 的 function descriptor
參數列表只允許一個引數傳入
/* object::instanceMethod */
System.out.println("\n== object::instanceMethod ==");
MethodReference ref = new MethodReference();
strList.forEach(ref::instancePrint);
/**** Class::instanceMethod ****/
System.out.println("\n== Class::instanceMethod ==");
strList.forEach(System.out::print);
}
private static void staticPrint(String s) {
System.out.print(s + ",");
}
private void instancePrint(String s) {
System.out.print(s + ",");
}
private static void xxx(String s) {
}
private static String yyy(String s) {
return (s == null ? "" : s) + "...";
}
private static void zzz() {
}
private static void www(String s1, String s2) {
}
}
--
※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 114.37.155.168 (臺灣)
※ 文章網址: https://webptt.com/m.aspx?n=bbs/java/M.1566369273.A.05B.html
1F:→ pttworld: C_AND_CPP板至底文有一卡車貼程式碼網站 08/21 16:18
2F:→ ssccg: Java8的這些新增功能是設計在與舊有type相容的原則上,提供 08/21 18:33
3F:→ ssccg: function type寫法的支援,所以才用了functional interface 08/21 18:33
4F:→ ssccg: 這個其實只是interface,不是新創一種function type的做法 08/21 18:35
5F:→ ssccg: 好處就是讓舊有的API有可能無痛升級 08/21 18:37
感謝ss大,在看 lambda 前有看到 why lambda 的文章
該文舉了一個 filter apple 的例子,若新增需求,要一直新增或修改方法
透過設計的方式,有點像是用了 strategy pattern 來處理
然後使用 anonymous class 去解決,這是在 lambda 之前
然後 lambda 可以寫出更簡潔,更短的程式碼
是的,我看好像大部份介紹 lambda 提到的好處都是這樣
但是在後續的 Stream 會提到,並行處理,多執行緒處理等
換言之,導入 lambda 應該也是為了解決某些效能上的問題
不過現在頭痛的就是,java.util.function 是有定義好一些函式介面
但若我也定義了自己的函式介面,lambda 的 target type 是哪個不知道
雖說 java 8 設計的應該足以使用,不太需要自己定義函式介面了
然後看 Stream 時更亂了,一堆 lambda
然後我只能去看方法的參數到底是什麼型別
也就是目前如果要使用,我不知道如何快速的去應用這些 API
6F:→ ssccg: lambda expression和method reference都可以evaluate成 08/21 18:38
7F:→ ssccg: functional interface instance,但定義還是不一樣 08/21 18:39
8F:→ ssccg: JLS在這兩種expression的type定義,前者在需要void回傳時 08/21 18:48
9F:→ ssccg: lambda必須是statement或void-compatible block 08/21 18:51
10F:→ ssccg: 但後者在需要void的時候,不管reference method的回傳type 08/21 18:54
11F:→ ssccg: 理由大概也是讓舊有method能盡量無痛拿來reference 08/21 18:55
12F:→ ssccg: 但是lambda expression是全新的所以適合較嚴格的限制 08/21 18:59
所以答案在 Java Language Specification 有提到
感謝ss大的說明 :)
13F:→ ssccg: target type是看用在什麼地方,那個地方需要什麼type就會是 08/21 19:32
14F:→ ssccg: 什麼type,因為目的就是模擬function type而不管是哪個 08/21 19:33
15F:→ ssccg: interface type 08/21 19:34
16F:→ ssccg: 在function type的概念上,API參數宣告成Consumer<String> 08/21 19:37
17F:→ ssccg: 是代表需要一個String → void,是不是Consumer不重要 08/21 19:37
我現在要用 lambda 都必須先看 target type 的 function descriptor
例如 Predicate<T> 的 abstract method 為 boolean test(T t);
所以 function descriptor 是 T -> boolean
但是後面提到的,是不是 Consumer 不重要,這我看不懂
List<Dish> menuList = ...................
Stream<Dish> dishes = menuList.stream();
dishes.filter(d -> d.getCalories() < 400);
但我不能這樣寫啊
@FunctionalInterface
interface Gg {
boolean gg(Dish d);
}
Gg gg = d -> true;
dishes.filter(gg);
當然,這個例子蠻白痴的,我要用 API 本來就要知道他吃什麼參數
所以我不懂ss大說的,是不是 Consumer 不重要,是什麼意思?
或者是,我的腦袋根本還不知道 functional programming 的意思?
18F:→ ssccg: 以你的例子來說,假設有個method是void test(Gg gg) 08/21 22:02
19F:→ ssccg: 可以 dishes.filter(d -> d.getCalories() < 400); 08/21 22:02
20F:→ ssccg: 也可以 test(d -> d.getCalories() < 400); 08/21 22:03
21F:→ ssccg: d -> d.getCalories() < 400這個lambda "expression"本身是 08/21 22:03
22F:→ ssccg: 沒有固定是哪個Interface type,是看用在哪就是哪個type 08/21 22:04
23F:→ ssccg: 你下面的例子不行是因為d -> true這個lambda expression是 08/21 22:05
24F:→ ssccg: 用在一個assignment statement要求type是Gg這個Interface 08/21 22:05
25F:→ ssccg: 不是因為filter()不能接受d -> true 08/21 22:06
26F:→ ssccg: 在API參數中用FunctionalInterface是描述需要的function而 08/21 22:07
27F:→ ssccg: 不是需要的type,這是functional programming的精神 08/21 22:08
28F:→ ssccg: 雖然受限FunctionalInterface實作,先把lambda expression 08/21 22:10
29F:→ ssccg: 決定成某個Interface後就不能再變了,但平常寫程式lambda 08/21 22:11
30F:→ ssccg: expression通常是直接用在method invoke的參數 08/21 22:12
以 Stream<T> 的 filter(Predicate<? super T> predicate)
因為 Predicate 的 function descriptor 是 T -> boolean
所以我在使用 filter() 時
d -> d.getCalories() < 400 也可以
d -> d.getType == Dish.Type.MEAT 也可以
d -> true 也可以
它們都符合 T -> boolean
使用 interface 就是讓我們注意在邏輯的實做就好
而不是想著要把它們歸屬於某個 type (某個 class)
就像我宣告了一個 Gg 的 functional interface
它的 functional descriptor 和 Predicate 一樣
但是我的思維是,用 Gg 解決某一個需求
下次遇到另一個需求,又宣告另一個 Yy 來解另一個需求
雖然程式是在用 lambda expression
但思維和原本的,遇到一個需求就增加一個 method 沒兩樣
也就是說 Predicate<T> 允許各種符合 function descriptor 的實作
我只要關注在,我的實做邏輯是什麼
然後用 lambda expression 丟進去就對了
這種理解不知道是否正確?
或是簡單說,我的 design pattern 的功力太弱
思維總是不夠抽象,總是對 concreat class 寫程式這樣對吧?
還是要感謝ssccg大的解說就是了 :)
※ 編輯: jtorngl (114.37.155.168 臺灣), 08/21/2019 22:54:45