[問題] method reference

看板java作者 (DDFL)時間5年前 (2019/08/21 14:34), 5年前編輯推噓0(0030)
留言30則, 2人參與, 5年前最新討論串1/1
最近才開始看 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://www.ptt.cc/bbs/java/M.1566369273.A.05B.html

08/21 16:18, 5年前 , 1F
C_AND_CPP板至底文有一卡車貼程式碼網站
08/21 16:18, 1F

08/21 18:33, 5年前 , 2F
Java8的這些新增功能是設計在與舊有type相容的原則上,提供
08/21 18:33, 2F

08/21 18:33, 5年前 , 3F
function type寫法的支援,所以才用了functional interface
08/21 18:33, 3F

08/21 18:35, 5年前 , 4F
這個其實只是interface,不是新創一種function type的做法
08/21 18:35, 4F

08/21 18:37, 5年前 , 5F
好處就是讓舊有的API有可能無痛升級
08/21 18:37, 5F
感謝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

08/21 18:38, 5年前 , 6F
lambda expression和method reference都可以evaluate成
08/21 18:38, 6F

08/21 18:39, 5年前 , 7F
functional interface instance,但定義還是不一樣
08/21 18:39, 7F

08/21 18:48, 5年前 , 8F
JLS在這兩種expression的type定義,前者在需要void回傳時
08/21 18:48, 8F

08/21 18:51, 5年前 , 9F
lambda必須是statement或void-compatible block
08/21 18:51, 9F

08/21 18:54, 5年前 , 10F
但後者在需要void的時候,不管reference method的回傳type
08/21 18:54, 10F

08/21 18:55, 5年前 , 11F
理由大概也是讓舊有method能盡量無痛拿來reference
08/21 18:55, 11F

08/21 18:59, 5年前 , 12F
但是lambda expression是全新的所以適合較嚴格的限制
08/21 18:59, 12F
所以答案在 Java Language Specification 有提到 感謝ss大的說明 :)

08/21 19:32, 5年前 , 13F
target type是看用在什麼地方,那個地方需要什麼type就會是
08/21 19:32, 13F

08/21 19:33, 5年前 , 14F
什麼type,因為目的就是模擬function type而不管是哪個
08/21 19:33, 14F

08/21 19:34, 5年前 , 15F
interface type
08/21 19:34, 15F

08/21 19:37, 5年前 , 16F
在function type的概念上,API參數宣告成Consumer<String>
08/21 19:37, 16F

08/21 19:37, 5年前 , 17F
是代表需要一個String → void,是不是Consumer不重要
08/21 19:37, 17F
我現在要用 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 的意思?

08/21 22:02, 5年前 , 18F
以你的例子來說,假設有個method是void test(Gg gg)
08/21 22:02, 18F

08/21 22:02, 5年前 , 19F
可以 dishes.filter(d -> d.getCalories() < 400);
08/21 22:02, 19F

08/21 22:03, 5年前 , 20F
也可以 test(d -> d.getCalories() < 400);
08/21 22:03, 20F

08/21 22:03, 5年前 , 21F
d -> d.getCalories() < 400這個lambda "expression"本身是
08/21 22:03, 21F

08/21 22:04, 5年前 , 22F
沒有固定是哪個Interface type,是看用在哪就是哪個type
08/21 22:04, 22F

08/21 22:05, 5年前 , 23F
你下面的例子不行是因為d -> true這個lambda expression是
08/21 22:05, 23F

08/21 22:05, 5年前 , 24F
用在一個assignment statement要求type是Gg這個Interface
08/21 22:05, 24F

08/21 22:06, 5年前 , 25F
不是因為filter()不能接受d -> true
08/21 22:06, 25F

08/21 22:07, 5年前 , 26F
在API參數中用FunctionalInterface是描述需要的function而
08/21 22:07, 26F

08/21 22:08, 5年前 , 27F
不是需要的type,這是functional programming的精神
08/21 22:08, 27F

08/21 22:10, 5年前 , 28F
雖然受限FunctionalInterface實作,先把lambda expression
08/21 22:10, 28F

08/21 22:11, 5年前 , 29F
決定成某個Interface後就不能再變了,但平常寫程式lambda
08/21 22:11, 29F

08/21 22:12, 5年前 , 30F
expression通常是直接用在method invoke的參數
08/21 22:12, 30F
以 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
文章代碼(AID): #1TNENv1R (java)
文章代碼(AID): #1TNENv1R (java)