關於 JDK8 中方法引用的一個問題


作者:RednaxelaFX
鏈接:https://www.zhihu.com/question/45218076/answer/98632631
來源:知乎
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。

方法引用是當你想把一個方法當作一個“函數指針”傳給別的方法用時有用的。

例如說,我有個ArrayList想把里面每個元素都打印出來,每個元素一行。
那么Java 8之前會這樣寫:
  for (ElementType e : list) { System.out.println(e); } 
從Java 8開始,使用ArrayList的新API加上lambda表達式,我們可以這樣寫:
  list.forEach(e -> System.out.println(e)); 
而這里的lambda表達式的內容其實只不過就是把參數傳給了println()方法,而沒有做任何別的事情,所以可以進一步簡寫為:
  list.forEach(System.out::println); 

僅此而已。

重點:
  • System.out是一個PrintStream實例的引用;System.out::println 是對一個實例方法的引用
    • 該引用同時指定了對實例(System.out)的引用以及對方法(PrintStream::println)的引用
  • System.out::println 不是 System.out.println 的等價物;前者是一個方法引用表達式,而后者不能單獨作為一個表達式,而必須在后面跟上由圓括號包圍的參數列表來構成方法調用表達式。
  • System.out::println 可以看作 lambda表達式 e -> System.out.println(e) 的縮寫形式。
作者:陸萌萌
鏈接:https://www.zhihu.com/question/45218076/answer/125250908
來源:知乎
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。

無意間看到了,還是答一下吧,這個時間點上我想題主如果繼續學習的話應該早已經明白了問題的答案,所以這篇答案就算留給后面無意間看到而又對此困惑的朋友吧:

實際上這個方法引用僅僅是一個Lambda表達式簡化寫法的語法糖,System.out::print和System.out.print()的語義完全不同,就像R大所說,用::而不用.就是為了區分語義,即使用了“::”都造成了困惑,那么用“.”的話恐怕困惑會更嚴重吧。

如果后來的朋友看不明白這個解釋的話,那么首先就要明白Java8增加的這個Lambda表達式是干什么的,其實Lambda表達式主要解決的問題就是“如何把一個動作傳入一個方法?”在C/C++中,我們可以給函數傳入一個函數指針來做到這一點,C#中我們可以將一個委托傳入方法中;本質上即是將一個方法/函數當成參數傳給另一個方法/函數,那么之前Java是沒有類似的語法的,一直以來Java中是如何解決這個問題的呢?

事實上Java一直以來的解決方案都是在方法參數上接收一個接口,接口中有需要的動作(即方法),調用的時候只要傳入一個實現這個接口的對象即可間接地把方法傳進去,例如創建線程的代碼:
new Thread(new Runnable() { @Override public void run() { //動作 } }).start(); 
上面這個代碼,我們繞了這么大一圈,只是為了把run()傳進Thread的構造器而已,我們能不能直接把run()傳入呢?這里就出現了Java 8的Lambda表達式,實際上Lambda表達式就是一個匿名方法,它是這種形態的:
(方法參數列表) -> {方法體}
所以上面Thread的例子就能寫成:
new Thread(() -> { //動作 }).start(); 
因為run()的參數是空的,所以前面的括號不能省略,箭頭后面的方法體如果的單行的,可以省略{}大括號。回到這道題,如果出現了傳入的匿名方法有一個參數,而這個方法的方法體中又調用了另一個方法,剛好把當前的參數傳入這個方法中,例如:
List<String> list = Arrays.asList("真","可","愛"); list.forEach((String item) -> System.out.print(item)); //這里的參數列表具有一定的類型推倒功能,所以也可以進一步省略寫為: list.forEach(item -> System.out.print(item)); 
Java 8中List多了forEach方法,用於遍歷列表,而遍歷時做的事情會被當作動作傳入這個方法,如果您只是打印里面的值的話,就會寫成上面的樣子,而用到本題的語法糖中就是:
List<String> list = Arrays.asList("真","可","愛"); list.forEach(System.out::print); 

既然只是想把每次遍歷得到的String item傳入print方法,那么簡略的用一個“方法引用”來引用這個方法就行了,這里這個System.out::print僅僅是等效於item -> System.out.print(item),和System.out.print()的語義是不同的,這里僅僅表達的是將唯一的參數傳入System.out這個流對象的print方法的Lambda表達式而已,所以這里也不加括號;System.out.print()的語義就是直接調用方法了。這個語法糖除了用於普通方法外,如果是構造函數的方法引用,那么就是“ClassName::new”這種寫法。這就是上面這個現象我自己的理解。

如果您對此還想繼續深入學習的話,建議您學習Java 8增加的Stream<T> API,它是Java對於函數式編程的一個實踐,學習它有助於理解函數式編程的一些基本思想,並對Scala、Spark編程的學習有一定好處。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM