原創文章,轉載請標註出處:https://www.cnblogs.com/V1haoge/p/10755338.html
一、概述
JDK1.8引入了函數語言程式設計,重點包括函式式介面、lambda表示式、方法引用等。
所謂函數語言程式設計就是將函式(一段操作)作為一個基本單位進行傳遞。以前的Java中引數只能是具體的變數,函數語言程式設計打破這一規範,可以將整個方法作為一個引數傳遞。
Java畢竟是物件導向的程式語言,你要傳遞的東西,必須是一個類或介面的物件或者一個基本型別變數,所以Java就定義了函式式介面,用來承載傳遞的函式。
二、函式式介面
2.1 函式式介面
函式式介面是在JDK1.8中提出的新概念,但對應的卻是老結構,在以往版本的JDK中就已經存在這種結構,只是沒有定義化。
函式式介面就是隻有一個抽象方法的介面。常用的函式式介面有Runnable、Comparator等。
JDK1.8將這些介面取了一個統一的名稱函式式介面,為了規範化,同時避免使用者自定義函式式介面時錯誤的新增了其他的抽象方法,而定義了一個註解:@FunctionalInterface,凡是由該註解標註的介面,統統為函式式介面,強制性的只有一個抽象方法。
為了函式式介面的擴充套件,JDK對介面規範進行了進一步修改,介面中除了可以定義抽象方法之外,還能夠定義靜態方法,和預設方法,而且這兩種方法可以擁有自己的實現。其中靜態方法一般作為工具方法,而預設方法是可以被繼承重寫的,還能擁有一個預設的實現。除此之外,函式式介面中還可以重寫Object中定義的方法。
// 典型函式式介面
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
// 自定義函式式介面
@FunctionalInterface
public interface Itest {
void test();
boolean equals(Object obj);// 重寫Object中的方法
default void defaultMethod(){
// 這是一個預設方法
}
static void staticMethod(){
// 這是一個靜態方法
}
}
2.2 預定義的函式式介面
JDK 1.8為我們預定義了許多函式式介面,它們位於java.util.function包中。
序號 | 介面名 | 抽象方法 | 說明 | 備註 |
---|---|---|---|---|
1 | Supplier<T> | T get() | 無輸入引數,通過一系列操作產生一個結果返回 | 無中生有 |
2 | IntSupplier | int getAsInt() | 通過操作返回一個int值 | 無中生有 |
3 | LongSupplier | long getAsLong() | 通過操作返回一個long值 | 無中生有 |
4 | DoubleSupplier | double getAsDouble() | 通過操作返回一個double值 | 無中生有 |
5 | BooleanSupplier | boolean getAsBoolean() | 通過操作返回一個boolean值 | 無中生有 |
6 | Consumer<T> | void accept(T t) | 一個輸入引數,針對引數做一系列操作,無返回值 | 消費掉了 |
7 | IntConsumer | void accept(int value) | 一個int型輸入引數,針對引數做一系列操作,無返回值 | 消費掉了 |
8 | LongConsumer | void accept(long value) | 一個long型輸入引數,針對引數做一系列操作,無返回值 | 消費掉了 |
9 | DoubleConsumer | void accept(double value) | 一個double型輸入引數,針對引數做一系列操作,無返回值 | 消費掉了 |
10 | BiConsumer<T, U> | void accept(T t, U u) | 兩個輸入引數,針對引數做一系列操作,無返回值 | 消費掉了 |
11 | ObjIntConsumer<T> | void accept(T t, int value) | 兩個輸入引數,一個自定義型別T,另一個指定位int型,針對引數做一系列操作,無返回值 | 消費掉了 |
12 | ObjLongConsumer<T> | void accept(T t, long value) | 兩個輸入引數,一個自定義型別T,另一個指定位long型,針對引數做一系列操作,無返回值 | 消費掉了 |
13 | ObjDoubleConsumer<T> | void accept(T t, double value) | 兩個輸入引數,一個自定義型別T,另一個指定位double型,針對引數做一系列操作,無返回值 | 消費掉了 |
14 | Function<T, R> | R apply(T t) | 一個引數,一個返回值,針對引數生成一個返回值 | 一因一果 |
15 | IntFunction<R> | R apply(int value) | 一個int引數,一個自定義返回值,根據給定的int引數生成一個返回值 | 一因一果 |
16 | LongFunction<R> | R apply(long value) | 一個long引數,一個自定義返回值,根據給定的long引數生成一個返回值 | 一因一果 |
17 | DoubleFunction<R> | R apply(double value) | 一個double引數,一個自定義返回值,根據給定的double引數生成一個返回值 | 一因一果 |
18 | ToIntFunction<T> | int applyAsInt(T value) | 一個引數,針對引數生成一個int返回值 | 一因一果 |
19 | ToLongFunction<T> | long applyAsLong(T value) | 一個引數,針對引數生成一個long返回值 | 一因一果 |
20 | ToDoubleFunction<T> | double applyAsDouble(T value) | 一個引數,針對引數生成一個double返回值 | 一因一果 |
21 | BiFunction<T, U, R> | R apply(T t, U u) | 兩個輸入引數,一個返回值,根據引數生成一個返回值 | 多因一果 |
22 | IntToDoubleFunction | double applyAsDouble(int value) | 一個int引數,根據引數生成一個double結果返回 | 一因一果 |
23 | IntToLongFunction | long applyAsLong(int value) | 一個int引數,根據引數生成一個long結果返回 | 一因一果 |
24 | LongToDoubleFunction | double applyAsDouble(long value) | 一個long引數,根據引數生成一個double結果返回 | 一因一果 |
25 | LongToIntFunction | int applyAsInt(long value) | 一個long引數,根據引數生成一個int果返回 | 一因一果 |
26 | DoubleToIntFunction | int applyAsInt(double value) | 一個double引數,根據引數生成一個int結果返回 | 一因一果 |
27 | DoubleToLongFunction | long applyAsLong(double value) | 一個double引數,根據引數生成一個long結果返回 | 一因一果 |
28 | ToIntBiFunction<T, U> | int applyAsInt(T t, U u) | 兩個輸入引數,根據引數生成一個int返回值 | 多因一果 |
29 | ToLongBiFunction<T, U> | long applyAsLong(T t, U u) | 兩個輸入引數,根據引數生成一個long返回值 | 多因一果 |
30 | ToDoubleBiFunction<T, U> | double applyAsDouble(T t, U u) | 兩個輸入引數,根據引數生成一個double返回值 | 多因一果 |
31 | Predicate<T> | boolean test(T t) | 一個引數,返回校驗boolean結果 | 校驗引數 |
32 | BiPredicate<T, U> | boolean test(T t, U u) | 兩個引數,返回校驗boolean結果 | 校驗引數 |
33 | IntPredicate | boolean test(int value) | 一個int引數,返回校驗boolean結果 | 校驗引數 |
34 | LongPredicate | boolean test(long value) | 一個long引數,返回校驗boolean結果 | 校驗引數 |
35 | DoublePredicate | boolean test(double value) | 一個double引數,返回校驗boolean結果 | 校驗引數 |
36 | UnaryOperator<T> | T apply(T t) | 一個T型引數,通過操作返回一個T型結果 | 一元操作 |
37 | IntUnaryOperator | int applyAsInt(int operand) | 一個int引數,通過操作返回一個int結果 | 一元操作 |
38 | LongUnaryOperator | long applyAsLong(long operand) | 一個long引數,通過操作返回一個long結果 | 一元操作 |
39 | DoubleUnaryOperator | double applyAsDouble(double operand) | 一個double引數,通過操作返回一個double結果 | 一元操作 |
40 | BinaryOperator<T> | T apply(T t1, T t2) | 兩個T型引數,通過操作返回一個T型結果 | 二元操作 |
41 | IntBinaryOperator | int applyAsInt(int left, int right) | 兩個int引數,通過操作返回一個int結果 | 二元操作 |
42 | LongBinaryOperator | long applyAsLong(long left, long right) | 兩個long引數,通過操作返回一個long結果 | 二元操作 |
43 | DoubleBinaryOperator | double applyAsDouble(double left, double right) | 兩個double引數,通過操作返回一個double結果 | 二元操作 |
三、Lambda表示式
Lambda表示式,簡化了匿名內部類的操作方式。
Lamnda表示式可以用在兩個地方,一種是集合遍歷,另一種就是替換匿名內部類。
前者基於Iterable介面和Map介面中定義的forEach方法,後者則依據函式式介面。
3.1 forEach方法
其實forEach方法是對函式式介面的有效利用,將遍歷的書寫流程簡化,我們不用再寫一大堆的for迴圈框架程式碼。
public class LanbdaTest {
public static void main(String[] args) {
List<String> list = Collections.EMPTY_LIST;
list.forEach(System.out::println);
Map<String,Object> map = Collections.EMPTY_MAP;
map.forEach((k,v) -> System.out.println(k + ":"+ v));
}
}
forEach方法的引數是Consumer或者BiConsumer,主要用於消費資源,即需要提供引數,但是沒有返回值的方法(函式或操作)。
forEach方法最開始是在Iterable介面和Map介面中定義的,這是以預設方法的方式定義的,分別以Consumer和BiConsumer作為入參。
Iterable中的forEach方法:
public interface Iterable<T> {
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
}
Iterable的實現類均可以通過重寫該方法來自定義遍歷的方式。
比如以陣列為底層結構的ArrayList、CopyOnWriteArrayList、CopyOnWriteArraySet等都是以普通for迴圈來實現的遍歷。而以連結串列為底層結構的LinkedList則沒有重寫forEach方法,採用預設方法中簡化的for迴圈,編譯器會對其進行處理,將其採用Iterator進行遍歷。
Map中的forEach方法:
public interface Map<K,V> {
default void forEach(BiConsumer<? super K, ? super V> action) {
Objects.requireNonNull(action);
for (Map.Entry<K, V> entry : entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
} catch(IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}
action.accept(k, v);
}
}
}
在常用的HashMap和TreeMap中都對該方法進行了重寫,HashMap採用陣列+連結串列(紅黑樹)的方式實現,但是遍歷的時候採用的就是陣列+連結串列雙重遍歷,因為在HashMap中的紅黑樹同時還是一個雙向連結串列。而TreeMap中則是使用樹結構的中序遍歷方式實現的。
3.2 替換匿名內部類
Lambda替換匿名內部類有一個前提,那就是這個匿名內部類的介面型別必須為函式式介面,如果不是函式式介面,是無法使用Lambda替換的。
常用的函式式介面為Runnable,使用匿名內部類方式如下:
public class LambdaTest {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("採用匿名內部類");
}
});
}
}
使用Lambda替換如下:
public class LambdaTest {
public static void main(String[] args) {
Thread t1 = new Thread(()->System.out.println("採用Lambda方式"));
}
}
Lambda表示式最大的作用其實就是替換匿名內部類,簡化這種寫法。
四、方法引用
方法引用出現的目的是為了解決所需的操作已經存在的情況。
當我們需要傳遞的操作已經存在,那就不必再費盡心思的再寫一個出來啦,直接使用方法引用來將已有的方法給它就行了。
方法引用使用“::”雙英文冒號組成的操作符來指定方法。
使用方法引用之後,你會很不適應,因為引數哪去啦???
是的,引數不再是顯式傳遞,採用方法引用之後,引數會自動傳遞,我們舉個例子看看簡單的原理:
public class LanbdaTest {
public static String getName(Supplier<String> supplier){
return supplier.get();
}
public static void main(String[] args) {
Person person = new Person("huahua");
System.out.println(getName(person::getName));
}
}
class Person{
private String name;
public Person(String name){
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
執行結果為:
huahua
解析:
首先我們使用了方法引用:person::getName,Person類中有已定義好的獲取name的方法,這裡就可以直接引用該方法。Supplier是供應者,可以無中生有,也就是不需要引數,產生一個返回值。
Person中的getName方法,明顯就符合Supplier的格式,沒有引數,但是返回了一個結果,所以這裡就可以直接傳遞person::getName。
方法引用的種類:
- 類的構造器引用:ArrayList::new、String[]::new
- 類的靜態方法引用:String::valueOf、Integer::valueOf
- 類的例項方法引用:String::length、Person::getName
- 物件的例項方法引用:sring::length、person::getName
方法引用於Lambda可以算是平等,並列的關係,Lambda用於自定義操作,方法引用用於引用已存在的操作。