函數式接口:
函數式接口,首先是一個接口,然后就是在這個接口里面只能有一個抽象方法,但是可以有多個非抽象方法的接口。
Java 8為函數式接口引入了一個新注解@FunctionalInterface,主要用於編譯級錯誤檢查,加上該注解,當你寫的接口不符合函數式接口定義的時候,編譯器會報錯。
函數式接口可以被隱式轉換為 lambda 表達式。
Java 8的庫幫你在java.util.function
包中引入了幾個新的函數式接口。我們接下來介紹 Predicate、Consumer和Function 三種函數式接口。
public interface Predicate<T> {
boolean test(T t);
}
public interface Comparator<T> {
int compare(T o1, T o2);
}
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
JDK 8之前已有的函數式接口:
- java.lang.Runnable
- java.util.concurrent.Callable
- java.security.PrivilegedAction
- java.util.Comparator
- java.io.FileFilter
- java.nio.file.PathMatcher
- java.lang.reflect.InvocationHandler
- java.beans.PropertyChangeListener
- java.awt.event.ActionListener
- javax.swing.event.ChangeListener
新定義的函數式接口:
java.util.function
中定義了幾組類型的函數式接口以及針對基本數據類型的子接口。
- Predicate -- 傳入一個參數,返回一個bool結果, 方法為
boolean test(T t)
- Consumer -- 傳入一個參數,無返回值,純消費。 方法為
void accept(T t)
- Function -- 傳入一個參數,返回一個結果,方法為
R apply(T t)
- Supplier -- 無參數傳入,返回一個結果,方法為
T get()
- UnaryOperator -- 一元操作符, 繼承Function,傳入參數的類型和返回類型相同。
- BinaryOperator -- 二元操作符, 傳入的兩個參數的類型和返回類型相同, 繼承BiFunction
函數式接口中可以額外定義多個Object的public方法一樣抽象方法:
接口最終有確定的類實現, 而類的最終父類是Object。 因此函數式接口可以定義Object的public方法。
如以下的接口依然是函數式接口:
@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
}
為什么限定public
類型的方法呢?因為接口中定義的方法都是public
類型的。 舉個例子,下面的接口就不是函數式接口:
interface WrongObjectMethodFunctionalInterface {
void count(int i);
Object clone(); //Object.clone is protected
}
因為Object.clone
方法是protected
類型。
聲明異常:
函數式接口的抽象方法可以聲明 可檢查異常
(checked exception)。 在調用目標對象的這個方法時必須catch這個異常。
public class FunctionalInterfaceWithException {
public static void main(String[] args) {
InterfaceWithException target = i -> {};
try {
target.apply(10);
} catch (Exception e) {
e.printStackTrace();
}
}
}
@FunctionalInterface
interface InterfaceWithException {
void apply(int i) throws Exception;
}
這和以前的接口/方法調用一樣。
但是,如果在Lambda表達式中拋出異常, 而目標接口中的抽象函數沒有聲明這個可檢查, 則此接口不能作為此lambda表達式的目標類型。
public class FunctionalInterfaceWithException {
public static void main(String[] args) {
InterfaceWithException target = i -> {throw new Exception();};
}
}
@FunctionalInterface
interface InterfaceWithException {
void apply(int i);
}
上面的例子中不能編譯, 因為lambda表達式要求的目標類型和InterfaceWithException
不同。 InterfaceWithException
的函數沒有聲明異常。
靜態方法:
函數式接口中除了那個抽象方法外還可以包含靜態方法。
Java 8以前的規范中接口中不允許定義靜態方法。 靜態方法只能在類中定義。 Java 8中可以定義靜態方法。
一個或者多個靜態方法不會影響SAM接口成為函數式接口。
下面的例子中FunctionalInterfaceWithStaticMethod
包含一個SAM: apply
,還有一個靜態方法sum
。 它依然是函數式接口。
@FunctionalInterface
interface FunctionalInterfaceWithStaticMethod {
static int sum(int[] array) {
return Arrays.stream(array).reduce((a, b) -> a+b).getAsInt();
}
void apply();
}
public class StaticMethodFunctionalInterface {
public static void main(String[] args) {
int sum = FunctionalInterfaceWithStaticMethod.sum(new int[]{1,2,3,4,5});
FunctionalInterfaceWithStaticMethod f = () -> {};
}
}
默認方法
Java 8中允許接口實現方法, 而不是簡單的聲明, 這些方法叫做默認方法,使用特殊的關鍵字default
。
因為默認方法不是抽象方法,所以不影響我們判斷一個接口是否是函數式接口。
@FunctionalInterface
interface InterfaceWithDefaultMethod {
void apply(Object obj);
default void say(String name) {
System.out.println("hello " + name);
}
}
class FunctionalInterfaceWithDefaultMethod {
public static void main(String[] args) {
InterfaceWithDefaultMethod i = (o) -> {};
i.apply(null);
i.say("default method");
}
}
InterfaceWithDefaultMethod
仍然是一個函數式接口。
泛型及繼承關系
接口可以繼承接口。 如果父接口是一個函數接口, 那么子接口也可能是一個函數式接口。 判斷標准依據下面的條件:
對於接口
I
, 假定M
是接口成員里的所有抽象方法的繼承(包括繼承於父接口的方法), 除去具有和Object的public的實例方法簽名的方法, 那么我們可以依據下面的條件判斷一個接口是否是函數式接口, 這樣可以更精確的定義函數式接口。
如果存在一個一個方法m, 滿足:
- m的簽名(subsignature)是M中每一個方法簽名的子簽名(signature)
- m的返回值類型是M中的每一個方法的返回值類型的替代類型(return-type-substitutable)
那么I就是一個函數式接口。
具體看參考中加粗的文章。
@FunctionalInterface:
Java 不會強制要求你使用@FunctionalInterface注解來標記你的接口是函數式接口, 然而,作為API作者, 你可能傾向使用@FunctionalInterface指明特定的接口為函數式接口, 這只是一個設計上的考慮, 可以讓用戶很明顯的知道一個接口是函數式接口。
@FunctionalInterface
public interface SimpleFuncInterface {
public void doWork();
}
如果你在一個不是函數式的接口使用@FunctionalInterface標記的話,會出現什么情況?編譯時出錯。
error: Unexpected @FunctionalInterface annotation
@FunctionalInterface
^
I is not a functional interface
multiple non-overriding abstract methods found in interface I
高階函數:
Function:
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
static <T> Function<T, T> identity() {
return t -> t;
}
}
其中實現了2個默認方法,分別compose,andThen,對應的函數表達為:
compose對應,體現嵌套關系;
andThen對應,轉換了嵌套的順序;
identity對應了一個傳遞自身的函數調用對應
從這里看出來,compose和andThen對於兩個函數f和g來說,
f.compose(g)
等價於g.andThen(f)
。
public class TestFunction {
public static void main(String[] args) {
Function<Integer, Integer> incr1 = x -> x + 1;
Function<Integer, Integer> multiply = x -> x * 2;
int x = 2;
System.out.println("f(x)=x+1,when x=" + x + ", f(x)=" + incr1.apply(x));
System.out.println("f(x)=x+1,g(x)=2x, when x=" + x + ", f(g(x))=" + incr1.compose(multiply).apply(x));
System.out.println("f(x)=x+1,g(x)=2x, when x=" + x + ", g(f(x))=" + incr1.andThen(multiply).apply(x));
System.out.println("compose vs andThen:f(g(x))=" + incr1.compose(multiply).apply(x) + "," + multiply.andThen(incr1).apply(x));
}
}
output:
f(x)=x+1,when x=2, f(x)=3
f(x)=x+1,g(x)=2x, when x=2, f(g(x))=5
f(x)=x+1,g(x)=2x, when x=2, g(f(x))=6
compose vs andThen:f(g(x))=5,5
拓展:
Function<Integer, Function<Integer, Integer>> makeAdder = z -> y -> z + y;
比如這個函數定義,參數是z,返回值是一個Function,這個Function本身又接受另一個參數y,返回z+y。於是我們可以根據這個函數,定義任意加法函數:
//high order function
Function<Integer, Function<Integer, Integer>> makeAdder = z -> y -> z + y;
x = 2;
//define add1
Function<Integer, Integer> add1 = makeAdder.apply(1);
System.out.println("f(x)=x+1,when x=" + x + ", f(x)=" + add1.apply(x));
//define add5
Function<Integer, Integer> add5 = makeAdder.apply(5);
System.out.println("f(x)=x+5,when x=" + x + ", f(x)=" + add5.apply(x));
由於高階函數接受一個函數作為參數,結果返回另一個函數,所以是典型的函數到函數的映射。
BiFunction提供了二元函數的一個接口聲明,舉例來說:
//binary function
BiFunction<Integer, Integer, Integer> multiply = (a, b) -> a * b;
System.out.println("f(z)=x*y, when x=3,y=5, then f(z)=" + multiply.apply(3, 5));
其輸出結果將是:f(z)=x*y, when x=3,y=5, then f(z)=15
。
二元函數沒有compose能力,只是默認實現了andThen。
有了一元和二元函數,那么可以通過組合擴展出更多的函數可能。
Function接口相關的接口包括:
- BiFunction :R apply(T t, U u);接受兩個參數,返回一個值,代表一個二元函數;
- DoubleFunction :R apply(double value);只處理double類型的一元函數;
- IntFunction :R apply(int value);只處理int參數的一元函數;
- LongFunction :R apply(long value);只處理long參數的一元函數;
- ToDoubleFunction:double applyAsDouble(T value);返回double的一元函數;
- ToDoubleBiFunction:double applyAsDouble(T t, U u);返回double的二元函數;
- ToIntFunction:int applyAsInt(T value);返回int的一元函數;
- ToIntBiFunction:int applyAsInt(T t, U u);返回int的二元函數;
- ToLongFunction:long applyAsLong(T value);返回long的一元函數;
- ToLongBiFunction:long applyAsLong(T t, U u);返回long的二元函數;
- DoubleToIntFunction:int applyAsInt(double value);接受double返回int的一元函數;
- DoubleToLongFunction:long applyAsLong(double value);接受double返回long的一元函數;
- IntToDoubleFunction:double applyAsDouble(int value);接受int返回double的一元函數;
- IntToLongFunction:long applyAsLong(int value);接受int返回long的一元函數;
- LongToDoubleFunction:double applyAsDouble(long value);接受long返回double的一元函數;
- LongToIntFunction:int applyAsInt(long value);接受long返回int的一元函數;
Operator:
Operator其實就是Function,函數有時候也叫作算子。算子在Java8中接口描述更像是函數的補充,和上面的很多類型映射型函數類似。
算子Operator包括:UnaryOperator和BinaryOperator。分別對應單元算子和二元算子。
單元算子的接口聲明如下:
@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {
static <T> UnaryOperator<T> identity() {
return t -> t;
}
}
二元算子的聲明:
@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T,T,T> {
public static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator) {
Objects.requireNonNull(comparator);
return (a, b) -> comparator.compare(a, b) <= 0 ? a : b;
}
public static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator) {
Objects.requireNonNull(comparator);
return (a, b) -> comparator.compare(a, b) >= 0 ? a : b;
}
}
很明顯,算子就是一個針對同類型輸入輸出的一個映射。在此接口下,只需聲明一個泛型參數T即可。對應上面的例子:
public class TestOperator {
public static void main(String[] args) {
UnaryOperator<Integer> add = x -> x + 1;
System.out.println(add.apply(1));
BinaryOperator<Integer> addxy = (x, y) -> x + y;
System.out.println(addxy.apply(3, 5));
BinaryOperator<Integer> min = BinaryOperator.minBy((o1, o2) -> o1 - o2);
System.out.println(min.apply(100, 200));
BinaryOperator<Integer> max = BinaryOperator.maxBy((o1, o2) -> o1 - o2);
System.out.println(max.apply(100, 200));
}
}
例子里補充一點的是,BinaryOperator提供了兩個默認的static快捷實現,幫助實現二元函數min(x,y)和max(x,y),使用時注意的是排序器可別傳反了:)
其他的Operator接口:(不解釋了)
- LongUnaryOperator:long applyAsLong(long operand);
- IntUnaryOperator:int applyAsInt(int operand);
- DoubleUnaryOperator:double applyAsDouble(double operand);
- DoubleBinaryOperator:double applyAsDouble(double left, double right);
- IntBinaryOperator:int applyAsInt(int left, int right);
- LongBinaryOperator:long applyAsLong(long left, long right);
Predicate:
predicate是一個謂詞函數,主要作為一個謂詞演算推導真假值存在,其意義在於幫助開發一些返回bool值的Function。本質上也是一個單元函數接口,其抽象方法test接受一個泛型參數T,返回一個boolean值。等價於一個Function的boolean型返回值的子集。
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate<T> negate() {
return (t) -> !test(t);
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
其默認方法也封裝了and、or和negate邏輯。
寫個小例子看看:
public class TestJ8Predicate {
public static void main(String[] args) {
TestJ8Predicate testJ8Predicate = new TestJ8Predicate();
testJ8Predicate.printBigValue(10, val -> val > 5);
testJ8Predicate.printBigValueAnd(10, val -> val > 5);
testJ8Predicate.printBigValueAnd(6, val -> val > 5);
//binary predicate
BiPredicate<Integer, Long> biPredicate = (x, y) -> x > 9 && y < 100;
System.out.println(biPredicate.test(100, 50L));
}
public void printBigValue(int value, Predicate<Integer> predicate) {
if (predicate.test(value)) {
System.out.println(value);
}
}
public void printBigValueAnd(int value, Predicate<Integer> predicate) {
if (predicate.and(v -> v < 8).test(value)) {
System.out.println("value < 8 : " + value);
} else {
System.out.println("value should < 8 at least.");
}
}
}
Output:
10
value should < 8 at least.
value < 8 : 6
true
Predicate在Stream中有應用,Stream的filter方法就是接受Predicate作為入參的。這個具體在后面使用Stream的時候再分析深入。
其他Predicate接口:
- BiPredicate:boolean test(T t, U u);接受兩個參數的二元謂詞
- DoublePredicate:boolean test(double value);入參為double的謂詞函數
- IntPredicate:boolean test(int value);入參為int的謂詞函數
- LongPredicate:boolean test(long value);入參為long的謂詞函數
Consumer:
看名字就可以想到,這像謂詞函數接口一樣,也是一個Function接口的特殊表達——接受一個泛型參數,不需要返回值的函數接口。
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
這個接口聲明太重要了,對於一些純粹consume型的函數,沒有Consumer的定義真無法被Function家族的函數接口表達。因為Function一定需要一個泛型參數作為返回值類型(當然不排除你使用Function來定義,但是一直返回一個無用的值)。比如下面的例子,如果沒有Consumer,類似的行為使用Function表達就一定需要一個返回值。
public class TestJ8Consumer {
public static void main(String[] args) {
Consumer<Integer> consumer = System.out::println;
consumer.accept(100);
//use function, you always need one return value.
Function<Integer, Integer> function = x -> {
System.out.println(x);
return x;
};
function.apply(100);
}
}
其他Consumer接口:
- BiConsumer:void accept(T t, U u);接受兩個參數
- DoubleConsumer:void accept(double value);接受一個double參數
- IntConsumer:void accept(int value);接受一個int參數
- LongConsumer:void accept(long value);接受一個long參數
- ObjDoubleConsumer:void accept(T t, double value);接受一個泛型參數一個double參數
- ObjIntConsumer:void accept(T t, int value);接受一個泛型參數一個int參數
- ObjLongConsumer:void accept(T t, long value);接受一個泛型參數一個long參數
Supplier
最后說的是一個叫做Supplier的函數接口,其聲明如下:
@FunctionalInterface
public interface Supplier<T> {
T get();
}
其簡潔的聲明,會讓人以為不是函數。這個抽象方法的聲明,同Consumer相反,是一個只聲明了返回值,不需要參數的函數(這還叫函數?)。也就是說Supplier其實表達的不是從一個參數空間到結果空間的映射能力,而是表達一種生成能力,因為我們常見的場景中不止是要consume(Consumer)或者是簡單的map(Function),還包括了new這個動作。而Supplier就表達了這種能力。
比如你要是返回一個常量,那可以使用類似的做法:
Supplier<Integer> supplier = () -> 1;
System.out.println(supplier.get());
這保證supplier對象輸出的一直是1。
如果是要利用構造函數的能力呢?就可以這樣:
Supplier<TestJ8Supplier> anotherSupplier;
for (int i = 0; i < 10; i++) {
anotherSupplier = TestJ8Supplier::new;
System.out.println(anotherSupplier.get());
}
這樣的輸出可以看到,全部的對象都是new出來的。
這樣的場景在Stream計算中會經常用到,具體在分析Java 8中Stream的時候再深入。
其他Supplier接口:
- BooleanSupplier:boolean getAsBoolean();返回boolean
- DoubleSupplier:double getAsDouble();返回double
- IntSupplier:int getAsInt();返回int
- LongSupplier:long getAsLong();返回long
總結
整個函數式接口的大概總結如下:
名稱 | 一元接口 | 說明 | 二元接口 | 說明 |
---|---|---|---|---|
一般函數 | Function | 一元函數,抽象apply方法 | BiFunction | 二元函數,抽象apply方法 |
算子函數(輸入輸出同類型) | UnaryOperator | 一元算子,抽象apply方法 | BinaryOperator | 二元算子,抽象apply方法 |
謂詞函數(輸出boolean) | Predicate | 一元謂詞,抽象test方法 | BiPredicate | 二元謂詞,抽象test方法 |
消費者(無返回值) | Consumer | 一元消費者函數,抽象accept方法 | BiConsumer | 二元消費者函數,抽象accept方法 |
供應者(無參數,只有返回值) | Supplier | 供應者函數,抽象get方法 | - | - |
參考:
Java 8函數式接口functional interface的秘密