1. 程式人生 > >深度解讀Java8-lambda表示式之方法引用

深度解讀Java8-lambda表示式之方法引用

先看個例子

import java.util.ArrayList;
import java.util.Arrays;
import static java.util.Comparator.comparing;

import java.util.Comparator;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;

/**
 * Created by kewangk on 2018/1/29.
 */
public class TestJava8 {
    public static void main(String[] args) {
        List<String> strings=Arrays.asList("hehe","","wonking","memeda");
        List<Integer> lengths=map(strings, (String s)->s.length());
        System.out.println(lengths);
        lengths.sort(comparing(Integer::intValue));
        lengths.sort((i1,i2)-> i1.compareTo(i2));
        System.out.println(lengths);
    }

    public static <T,R> List<R> map(List<T> list, Function<T,R> f){
        List<R> result=new ArrayList<R>(list.size());
        for(T t:list){
            result.add(f.apply(t));
        }
        return result;
    }

    public static List<Integer> filterOdd(List<Integer> list, Predicate<Integer> p){
        List<Integer> result=new ArrayList<>();
        for(Integer i: list){
            if(p.test(i)){
                result.add(i);
            }
        }
        return result;
    }
}

lengths.sort(comparing(Integer::intValue));

這行就用到了方法引用,初看有些晦澀。沒關係,我們來從外到內層層剝開它的外衣。

首先看List.sort(Comparator<? super E> c)這個方法

sort方法需要傳進一個Comparator,這是一個函式式介面,所以我們首先想到能用lambda表示式來傳參

lengths.sort(( i1, i2) -> i1.compareTo(i2));

所以你像上面這樣寫沒錯,但編譯器會抱怨一句

Inspection looks for Comparators defined as lambda expressions which could be expressed using methods like Comparator.comparing()

Can be replaced with Comparator.naturalOrder

這句話的意思是說,語法檢查器查詢定義為lambda表示式的Comparator,可以使用Comparator.comparing()方法來代替,並且還心細到給你找了一個現成的自然順序比較器給你開箱即用(我們先忽略這個)。

不明白這句話的意思?這其實就是在鼓勵你是用lambda表示式。

你就奇怪了,我不是已經用了lambda表示式嘛?幹嘛還非要我換別的用法?

我們知道,lambda表示式其實就是提供了某個函式式介面的一個實現,只不過它本身不攜帶自己實現的哪個介面的資訊,而是由編譯器進行型別推斷。

既然lambda表示式本身不攜帶實現介面的資訊,那麼我們需要知道(一眼就看出來的那種,彎都不帶拐的)某個方法到底需要哪種型別的lambda表示式怎麼辦呢?

所以Java API的設計師們幫我們設計出這樣一個靜態方法

public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
        Function<? super T, ? extends U> keyExtractor)
{
    Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
        (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}

從這個方法的實現來看,我們可以看出設計師們設計此方法的更多用意:

為了相容老介面Comparable,去看看API可以知道,Comparable介面1.2版本開始出現

為了實現方法引用,那麼方法引用又是為了實現什麼?現在你還不能理解,接著看下去

為了更優雅的程式碼

為了愛

為了和平

箇中奧妙,真無窮無盡也

再來看看這個comparing方法做了什麼

返回一個Comparator(正是sort方法需要的),傳入一個Function,另一個函式式介面,來看看Function的函式描述符(再次感受一下lambda表示式的簡潔優美)

Function<T,R>  T->R

將T變成R,OOP式的表達就是,將一種物件轉化成另一種物件,這就是Function的全部

具體到這個例子中來,看看Function實現了從什麼到什麼的轉化

Function<? super T, ? extends U>,這個泛型限定的是,源物件是T或T的一個父類,目標物件是U或U的一個子類。T沒有更具體的限制,那麼來看U,U是實現了Comparable介面的一個物件

現在清楚了,Function要做的事情就是,將T轉化成一個Comparable物件,T不管它是什麼型別,只要它實現了Comparable介面,這個轉換就會成功

所以這個Function的作用用一句話總結來說就是,將實現了Comparable介面的兩個物件轉化成Comparable物件進行比較,整個比較邏輯,又是作為一個lambda表示式形式的Compatator介面進行返回

至此,最開始的那行程式碼已經被我們剝得只剩褲衩了。感覺是不是很美妙~~

再來看看Integer::intValue

可以肯定,這是一個lambda表示式形式的Function,那麼它代表前面說的函式描述符中T->R的哪一個呢?
很顯然,它代表T,之前我們分析Function的時候已經確定,R就是Comparable,但是一直沒說T從哪來。

專業來講,它是一個方法引用,即引用Integer中的intValue方法。

與這個引用等價的lambda表示式是:(Integer i)->i.intValue()

這個lambda表示式的簽名不正是與Function的apply()方法簽名一致嗎

如果我們把這個lambda對
return (Comparator<T> & Serializable)
            (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
這段程式碼中的keyExtractor.apply(c1)進行替換,就變成了這樣的程式碼:
return (Comparator<T> & Serializable)(c1, c2) -> c1.compareTo(c2);
是不是很眼熟?沒錯,兜兜轉轉轉了一圈又特麼回來了,其實中間實際還進行了基本型別裝箱拆箱的動作。

準確來說,轉換過程是這樣滴:
Integer->int->Integer->Comparable

總結下來:這個comparing幫我們做了下面幾件事情:
將重複的方法呼叫進行了摺疊,避免重複呼叫
限制可以比較的型別,通過指定Function的轉換目標為Comparable,限制源型別必須實現了Comparable接口才能比較(不過這個理由貌似很牽強)

所謂大道至簡,它只是簡約,而不簡單。也不要懼怕這大道,其背後一定是由諸多簡單的規則環環相扣而來,只要一層一層分析下去,真理自會展現。