Java 8 - 05 方法引用


在這裏插入圖片描述

Pre

先來看段代碼

  Comparator<Enginner> enginnerComparator = new Comparator<Enginner>() {
            @Override
            public int compare(Enginner o1, Enginner o2) {
                return o1.getJob().compareTo(o2.getJob());
            }
        };

 enginnerComparator.compare(new Enginner("Java",18),new Enginner("Go",10));
 List<Enginner> enginnerList = Arrays.asList(new Enginner("Java",18),new Enginner("Go",10));
 System.out.println("0enginnerList:" + enginnerList);

 enginnerList.sort(enginnerComparator);
 System.out.println("1enginnerList:" + enginnerList);

排序嘛 ,是不是很長

使用方法引用,一行代碼搞定,如下:

 enginnerList.sort(Comparator.comparing(Enginner::getJob));
 System.out.println("2enginnerList:" + enginnerList);

在這裏插入圖片描述

方法引用讓你可以重複使用現有的方法定義,並像Lambda一樣傳遞它們。在一些情況下比起使用Lambda表達式, 更易讀 。上面的栗子就是藉助了Java 8 API ,用方法引用寫的一個排序的例子。


方法引用

方法引用可以被看作僅僅調用特定方法的Lambda的一種快捷寫法。它的基本思想是,如果一個Lambda代表的只是“直接調用這個方法”,那最好還是用名稱來調用它,而不是去描述如何調用它。

實際上,方法引用就是讓你根據已有的方法實現來創建Lambda表達式。 顯式地指明方法的名稱,代碼的可讀性會更好 。

當你需要使用方法引用時,目標引用放在分隔符 :: 前,方法的名稱放在後面

Enginner::getJob

就是引用了 Enginner類中定義的方法 getJob 。 請記住,不需要括號,因爲你沒有實際調用這個方法。

方法引用就是Lambda表達式 (Enginnera) -> a.getJob() 的快捷寫法

再來看幾個等效的例子 加深下印象

(Enginner a) -> a.getJob()    等價於  Enginner ::getJob

做下實驗


public class MethodReferrenceDemo {

    public static <T, R> R doSomething(T t, Function<T, R> f) {
        return f.apply(t);
    }

    public static void main(String[] args) {
        Function<Enginner, String> ef = (Enginner e) -> e.getJob();
        System.out.println(doSomething(new Enginner("Java", 18), ef));

        Function<Enginner, String> ef2 = Enginner::getJob;
        System.out.println(doSomething(new Enginner("Java", 18), ef2));

    }
}

在這裏插入圖片描述

還比如

() -> Thread.currentThread().dumpStack()  等價於 Thread.currentThread()::dumpStack
(str, i) -> str.substring(i)  等價於  String::substring
(String s) -> System.out.println(s)  等價於  System.out::println

我們可以把方法引用看作針對僅僅涉及單一方法的Lambda的語法糖,因爲你表達同樣的事情
時要寫的代碼更少了。


如何構建方法引用

方法引用主要有三類。

指向靜態方法的方法引用

舉個例子 : Integer 的 parseInt 方法,寫作 Integer::parseInt

        Function<String, Integer> stringIntegerFunction2 = (String s) -> Integer.parseInt(s);
        Function<String, Integer> stringIntegerFunction3 = Integer::parseInt;

        System.out.println(stringIntegerFunction2.apply("18"));
        System.out.println(stringIntegerFunction3.apply("18"));

指向任意類型實例方法的方法引用

舉個例子 : String 的 length 方 法 , 寫 作 String::length

  Function<String, Integer> stringIntegerFunction = (String s) -> s.length();
  Function<String, Integer> stringIntegerFunction1 = String::length;

  System.out.println(stringIntegerFunction.apply("abc"));
  System.out.println(stringIntegerFunction1.apply("abc"));

類似於 String::length 方法引用的思想就是你在引用一個對象的方法,而這個對象本身是Lambda的一個參數。

例如,Lambda表達式 (String s) -> s.toUppeCase() 可以寫作 String::toUpperCase


指向現有對象的實例方法的方法引用

假設你有一個局部變量 eng用於存放 Enginner 類型的對象,它支持實例方法 getValue ,那麼你就可以寫 eng::getValue

這種寫法是我們在Lambda中調用一個已經存在的外部對象中的方法。 例如,Lambda表達式()->eng.getValue() 可以寫作 eng::getValue


再來看幾個例子, 將Lambda表達式重構爲等價的方法引用

lambda :   args -> ClassName.staticMethod(args)

等價於  (1)

方法引用:  className::staticMethod

lambda :   (arg0 ,rest)-> argo.instanceMethod(rest)   (arg0是ClassName類型的)
 
等價於   (2)

方法引用:  ClassName::instanceMethod

lambda :   (args)-> expr.instanceMethod(args)    
 
等價於

方法引用:  expr::instanceMethod

請注意,還有針對構造函數、數組構造函數和父類調用(super-call)的一些特殊形式的方法引用。

比方說你想要對一個字符串的 List 排序,忽略大小寫。 List 的 sort 方法需要一個 Comparator 作爲參數 。 我們知道 Comparator 描述了 一個具有 (T, T) -> int 簽名的函數描述符。你可以利用 String 類中的 compareToIgnoreCase方法來定義一個Lambda表達式。

 List<String> list = Arrays.asList("apple","pear","banana");
 list.sort((s1,s2)-> s1.compareToIgnoreCase(s2));
  

Lambda表達式的簽名與 Comparator 的函數描述符兼容。利用前面所述的方法,這個例子可以使用方法引用改成下面的樣子

 list.sort(String::compareToIgnoreCase);

請注意,編譯器會進行一種與Lambda表達式類似的類型檢查過程,來確定對於給定的函數式接口,這個方法引用是否有效:方法引用的簽名必須和上下文類型匹配

來個小測驗吧

測驗:方法引用
下列Lambda表達式的等效方法引用是什麼?

(1) Function<String, Integer> stringToInteger =
(String s) -> Integer.parseInt(s);
(2) BiPredicate<List<String>, String> contains =
(list, element) -> list.contains(element);


答案如下。
(1) 這個Lambda表達式將其參數傳給了 Integer 的靜態方法 parseInt 。這種方法接受一
個需要解析的 String ,並返回一個 Integer 。

因此,可以使用 Lambda表達式調用靜態方法來重寫Lambda表達式,如下所示:
Function<String, Integer> stringToInteger = Integer::parseInt;


(2) 這個Lambda使用其第一個參數,調用其 contains 方法。由於第一個參數是 List 類型
的,你可以使用剛纔的(2) 如下:
BiPredicate<List<String>, String> contains = List::contains;


這是因爲,目標類型描述的函數描述符是  (List<String>,String) -> boolean ,而
List::contains 可以被解包成這個函數描述符。




構造函數引用

對於一個現有構造函數,我們可以利用它的名稱和關鍵字 new 來創建它的一個引用:ClassName::new 。它的功能與指向靜態方法的引用類似。

例如,假設有一個構造函數沒有參數。它適合 Supplier 的簽名 () -> Enginner。

Enginner的構造函數
在這裏插入圖片描述

我們可以這樣做:

  
    // 構造函數引用指向默認Enginner的構造函數
     Supplier<Enginner> supplier = Enginner::new;
     
  // 調用 Supplier 的 get 方法 將產生一個新的 enginner 
     Enginner enginner = supplier.get();

等價於

    Supplier<Enginner> s = () -> new Enginner();// 利用默認構造函數創建 Enginner的Lambda表達式
    Enginner supplier2 = s.get();// 調用 Supplier 的 get 方法 將產生一個新的 Enginner 

如果Enginner構造函數的簽名是 Enginner(String job) ,那麼它就適合 Function 接口的籤
名,我們可以這樣寫:

// 指向 Enginner(String job) 的構造函數引用
  Function<String ,Enginner> f2 =  Enginner::new; 
  
// 調用該 Function 函數的 apply 方法,並給出職位,將產生一個 Enginner 
  Enginner e2 =f2.apply("Java");
  System.out.println(e2.getJob());

如果你有一個具有兩個參數的構造函數 Enginner(String job, Integer age) ,那麼
它就適合 BiFunction 接口的簽名 . 兩個參數的 使用BiFunction 即可 (Bi = Binary )

  BiFunction<String ,Integer,Enginner> f3 = Enginner::new;
  Enginner e4 = f3.apply("Java",18);
  System.out.println(e4 .getJob() + " - " +  e4.getAge());

上面是使用方法引用,如果直接用lambda呢? 如下

     BiFunction<String,Integer,Enginner> f4 = (str , age)-> new Enginner(str, age);
    Enginner ee =f4.apply("Go", 10);
    System.out.println(ee .getJob() + " - " +  ee.getAge());

在這裏插入圖片描述


自定義構造函數引用

上面的栗子我們將有零個、一個、兩個參數的構造函數轉變爲構造函數引用。那要怎麼樣才能對具有三個參數的構造函數,比如 Color(int, int, int), 使用構造函數引用呢?

構造函數引用的語法是 ClassName::new ,那麼在這個例子裏面就是Color::new 。但是你需要與構造函數引用的簽名匹配的函數式接口。但是語言本身並沒有提供這樣的函數式接口,你可以自己創建一個:

public interface TriFunction<T, U, V, R>{
    R apply(T t, U u, V v);
}

現在你可以像下面這樣使用構造函數引用了:

TriFunction<Integer, Integer, Integer, Color> colorFactory = Color::new;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章