1. 程式人生 > >Java8————方法引用

Java8————方法引用

譯者注:本篇部落格翻譯自Oracle官方教程《Method References》。作為Java 8 新特性Lambda表示式的引申概念,博主依然採用官方文件的方式來學習這一重要的概念。希望對各位同道有所幫助。

方法引用

使用Lambda表示式建立匿名方法。但是,有時候Lambda表示式什麼都沒做,僅僅是呼叫了一個已經存在的方法。這種情況下,引用已存在方法的方法名通常是更清晰的。方法引用允許你這麼做,它是一種簡潔的、可讀性強的有名方法的Lambda表示式。

再次思考我們在Lambda 表示式部分(即上一篇翻譯《Java8————Lambda表示式(二)》)中討論的Person類:

public class Person {

    public enum Sex {
        MALE, FEMALE
    }

    String name;
    LocalDate birthday;
    Sex gender;
    String emailAddress;

    public int getAge() {
        // ...
    }
    
    public Calendar getBirthday() {
        return birthday;
    }    

    public static int compareByAge(Person a, Person b) {
        return a.birthday.compareTo(b.birthday);
    }}

假設你的應用中的成員全都包含在一個數組中,並且你希望把他們以年齡進行排序。你可以使用下面的程式碼:

Person[] rosterAsArray = roster.toArray(new Person[roster.size()]);

class PersonAgeComparator implements Comparator<Person> {
    public int compare(Person a, Person b) {
        return a.getBirthday().compareTo(b.getBirthday());
    }
}
        
Arrays.sort(rosterAsArray, new PersonAgeComparator());

呼叫的sort方法的方法簽名(譯者注:方法簽名是包含方法名、引數列表等在內的方法資訊)如下:

static <T> void sort(T[] a, Comparator<? super T> c)

注意,Comparator介面是一個函式介面。因此,你可以使用Lambda表示式而不是去定義並隨後建立一個實現了Comparator的類的例項:

Arrays.sort(rosterAsArray,
    (Person a, Person b) -> {
        return a.getBirthday().compareTo(b.getBirthday());
    }
);

但是,比較兩個Person物件生日的方法已經存在於Person.compareByAge方法當中。你可以Lambda表示式的body中呼叫:

Arrays.sort(rosterAsArray,
    (a, b) -> Person.compareByAge(a, b)
);

因為這個Lambda表示式呼叫一個存在的方法,因此,你可以使用方法引用來代替Lambda表示式:

Arrays.sort(rosterAsArray, Person::compareByAge);

方法引用 Person::compareByAge語義上與Lambda表示式(a, b) -> Person.compareByAge(a, b) 是一樣的。都具備以下特性:

1、它的引數列表:(Person , Person)從Comparator<Person>.compare複製過來。

2、它的主體呼叫方法Person.compareByAge()

方法引用的種類

有如下四種方法引用:

種類 距離
Reference to a static method ContainingClass::staticMethodName
Reference to an instance method of a particular object containingObject::instanceMethodName
Reference to an instance method of an arbitrary object of a particular type ContainingType::methodName
Reference to a constructor ClassName::new
  • 靜態方法引用

方法引用如:Person::compareByAge 就是對靜態方法的引用。

  • 特定物件的例項方法引用

下面的例子就是對特定物件的例項方法引用:

class ComparisonProvider {
    public int compareByName(Person a, Person b) {
        return a.getName().compareTo(b.getName());
    }
        
    public int compareByAge(Person a, Person b) {
        return a.getBirthday().compareTo(b.getBirthday());
    }
}
ComparisonProvider myComparisonProvider = new ComparisonProvider();
Arrays.sort(rosterAsArray, myComparisonProvider::compareByName);

方法引用:myComparisonProvider::compareByName,呼叫了myComparisonProvider物件的方法compareByName。JRE會推斷出方法的引數型別,這個例子中是(Person, Person)。

  • 特定型別的任意物件的例項方法引用

下面的例子是特定型別的任意物件的例項方法引用:

String[] stringArray = { "Barbara", "James", "Mary", "John",
    "Patricia", "Robert", "Michael", "Linda" };
Arrays.sort(stringArray, String::compareToIgnoreCase);

與Lambda表示式等價的方法引用 String::compareToIgnoreCase 有一個正式的引數列表:(String a, String b), a 和b 都是用來更好的描述這個例子的任意的名稱。這個方法引用會呼叫方法:a.compareToIgnoreCase(b)。

  • 構造器的方法引用

你可以像使用靜態方法引用的方式那樣使用new關鍵字來引用構造器。下面的方法將元素從一個集合中拷貝到另一個集合中。

public static <T, SOURCE extends Collection<T>, DEST extends Collection<T>>
    DEST transferElements(
        SOURCE sourceCollection,
        Supplier<DEST> collectionFactory) {
        
        DEST result = collectionFactory.get();
        for (T t : sourceCollection) {
            result.add(t);
        }
        return result;
}

函式介面Supplier包含一個沒有引數並且返回一個物件的方法get()。因此,你可以像下面這樣,通過Lambda表示式呼叫transferElements()方法:

Set<Person> rosterSetLambda =
    transferElements(roster, () -> { return new HashSet<>(); });

你可以像下面這樣使用構造器引用來取代Lambda表示式:

Set<Person> rosterSet = transferElements(roster, HashSet::new);

Java編譯器會推斷出你希望建立一個包含元素型別為Person的HashSet集合。你可以像下面這樣來指定,二選一。

Set<Person> rosterSet = transferElements(roster, HashSet<Person>::new);