Java 8 中的方法引用
一、原理概要
lambda 表示式,可以作為某些匿名內部類的替代。主要目的是調用該內部類中的方法,而該方法的實現(重寫)由 lambda表示式決定。
通常,我們可能不關心匿名內部類中的具體方法(被重寫的方法),而只關心該方法是怎麽被重寫的(方法的實現)。因此,我們可以構造一個中間對象(通常是接口,比如 Funtion),該接口擁有一個需要該重寫的方法(比如 Function 對應的方法是 apply)。
二、如何使用
在實際書寫時,可以只寫出(傳遞的參數)與{方法的實現},或者只標出實現過程的 調用者的和其方法名(使用冒號分隔)。
Function<Person, Integer> getAge = Person::getAge; // 傳參數調用 getAge 方法 Integer age = getAge.apply(p);
註釋:
1. 比如 Function<T,R>
,T
表示傳入類型,R
表示返回類型。比如,表達式 person -> person.getAge();
,傳入參數是 person
,返回值是 person.getAge()
,那麽方法引用 Person::getAge
就對應著 Function<Person,Integer>
類型。
2. 使用雙冒號的方式會返回這個對象,當你調用該對象中的 apply 方法時,就會調用你之前傳進去的方法。
3. 這個過程類似一個函數的閉包。當你使用雙冒號的方式,會把你的類和方法傳入並生成一個 Function對象。該對象會在之後某個時間被調用,此時它會調用之前你傳入這個對象中的方法。
4. 三種方式對比
// old way List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7); for (Integer n : list) { System.out.println(n); } // 使用 -> 的 Lambda 表達式 list.forEach(n -> System.out.println(n)); // 使用 :: 的 Lambda 表達式 list.forEach(System.out::println);
三、關於函數式接口
1. Java 8中允許接口實現方法, 而不是簡單的聲明。需要使用關鍵字 default
2. Java8 中的 函數式接口中除了定義抽象方法外還可以包含靜態方法。
@FunctionalInterface interface FunctionalInterfaceWithStaticMethod { static int sum(int[] array) { return Arrays.stream(array).reduce((a, b) -> a+b).getAsInt(); } void apply(); }
註. @FunctionalInterface註解,主要用於編譯級錯誤檢查,加上該註解,當你寫的接口不符合函數式接口定義的時候,編譯器會報錯。
3.一般情況下函數式接口只會定義一個抽象方法,但是接口最終有確定的類實現, 而類的最終父類是Object。 因此函數式接口可以定義Object 中的public方法,protected 方法不行。
@FunctionalInterface public interface ObjectMethodFunctionalInterface { void count(int i); String toString(); //same to Object.toString int hashCode(); //same to Object.hashCode boolean equals(Object obj); //same to Object.equals }
4. util 包中的函數式接口
java.util.function
中定義了幾組類型的函數式接口以及針對基本數據類型的子接口。
- Predicate -- 傳入一個參數,返回一個bool結果, 方法為
boolean test(T t)
- Consumer -- 傳入一個參數,無返回值,純消費。 方法為
void accept(T t)
- Function<t,r> -- 傳入一個參數,返回一個結果,方法為
R apply(T t)
- Supplier -- 無參數傳入,返回一個結果,方法為
T get()
- UnaryOperator -- 一元操作符, 繼承Function<t,t>,傳入參數的類型和返回類型相同。
- BinaryOperator -- 二元操作符, 傳入的兩個參數的類型和返回類型相同, 繼承BiFunction
Java 8 中的方法引用