天天看點

Java 8 函數式接口

本文節譯自 GeeksForGeeks

Java 8 函數式接口

“函數式接口(Functional Interface)”這個名稱來源于"函數式程式設計(Functional  Programming)",

我們最常用的面向對象程式設計(Java)屬于指令式程式設計(Imperative Programming)這種程式設計範式。常見的程式設計範式還有邏輯式程式設計(Logic Programming),函數式程式設計(Functional Programming)。

函數式程式設計作為一種程式設計範式,在科學領域,是一種編寫計算機程式資料結構和元素的方式,它把計算過程當做是數學函數的求值,而避免更改狀态和可變資料。

函數式程式設計并非近幾年的新技術或新思維,距離它誕生已有大概50多年的時間了。它一直不是主流的程式設計思維,但在衆多的所謂頂級程式設計高手的科學工作者間,函數式程式設計是十分盛行的。

什麼是函數式程式設計?簡單的回答:一切都是數學函數。函數式程式設計語言裡也可以有對象,但通常這些對象都是恒定不變的 —— 要麼是函數參數,要什麼是函數傳回值。函數式程式設計語言裡沒有 for/next 循環,因為這些邏輯意味着有狀态的改變。相替代的是,這種循環邏輯在函數式程式設計語言裡是通過遞歸、把函數當成參數傳遞的方式實作的。

舉個例子:

a = a + 1           
這段代碼在普通成員看來并沒有什麼問題,但在數學家看來确實不成立的,因為它意味着變量值得改變。
  • 函數式程式設計的第一個特點就是可以把函數作為參數傳遞給另一個函數,也就是所謂的高階函數。例如,對數組進行排序,可以傳入一個排序函數作為參數:
String[] array = { "orange", "Pear", "Apple" };
Arrays.sort(array, String::compareToIgnoreCase);           
  • 函數式程式設計的第二個特點就是可以傳回一個函數,這樣就可以實作閉包或者惰性計算:
以上兩個特點還僅僅是簡化了代碼。從代碼的可維護性上講,函數式程式設計最大的好處是引用透明,即函數運作的結果隻依賴于輸入的參數,而不依賴于外部狀态,是以,我們常常說函數式程式設計沒有副作用。

個人粗淺的了解是,函數式接口因為隻有一個方法,是以該接口的作用就像一個函數一樣,隻能對外提供單一功能但确定的計算。

言歸正傳

函數式接口是這樣的一種接口:包含且僅包含一個抽象方法的接口。在Java 8 中,我們可以使用lambada表達式來作為函數式接口的實作。在Java 8中接口可以有預設方法和靜态方法,是以一個函數式接口也可以有多個預設方法。

Runnale, ActionListener, Comparable 都是函數式接口。

在Java 8 以前,我們需要使用匿名内部類,或者顯式地的實作接口:

1 // Java program to demonstrate functional interface 
 2   
 3 class Test 
 4 { 
 5     public static void main(String args[]) 
 6     { 
 7         // create anonymous inner class object 
 8         new Thread(new Runnable() 
 9         { 
10             @Override
11             public void run() 
12             { 
13                 System.out.println("New thread created"); 
14             } 
15         }).start(); 
16     } 
17 }       

Output:

New thread created      

從 Java 8 開始,我們可以将lambda表達式指派給一引用了函數式接口的對象:

// Java program to demonstrate Implementation of 
// functional interface using lambda expressions 
  
class Test 
{ 
  public static void main(String args[]) 
  { 
  
    // lambda expression to create the object 
    new Thread(()-> 
       {System.out.println("New thread created");}).start(); 
  } 
}       

@FunctionalInterface 注解

@FunctionalInterface 注解可以加到函數式接口的定義上方,用來強制檢查所聲明的接口是函數式接口,隻包含了一個抽象方法。如果在有@FunctionalInterface注解的接口中聲明了超過一個的抽象方法,編譯器會抛出 ‘Unexpected @FunctionalInterface annotation’ 異常。

1 // Java program to demonstrate lamda expressions to implement 
 2 // a user defined functional interface. 
 3   
 4 @FunctionalInterface
 5 interface Square 
 6 { 
 7     int calculate(int x); 
 8 } 
 9   
10 class Test 
11 { 
12     public static void main(String args[]) 
13     { 
14         int a = 5; 
15   
16         // lambda expression to define the calculate method 
17         Square s = (int x)->x*x; 
18   
19         // parameter passed and return type must be 
20         // same as defined in the prototype 
21         int ans = s.calculate(a); 
22         System.out.println(ans); 
23     } 
24 } 
25 Output:
26 
27 25      

java.util.function 包

Java 8的java.util.function包裡面有很多内置的函數式接口:

  • Predicate : Predicate 接口包含一個抽象方法test, test方法會對輸入的參數做一個評判,輸出一個布爾值作為評判結果,至于評判的criteria交給具體的實作去做。Predicate的函數原型如下:
1 public interface Predicate
2 {
3    public boolean test(T  t);
4  }      
  • BinaryOperator : 見名知意,BinaryOperator 是一個二進制輸入的函數式接口,它包含一個apply抽象方法,接收兩個相同類型的參數,傳回一個與輸入類型相同的結果。
1 public interface BinaryOperator 
2 {
3      public T apply(T x, T y);
4 }         
  • Function : Function 接口有一個抽象方法apply, 接收一個類型T的輸入,傳回一個類型R的結果。這個名字也很展現用途,Function 是函數的意思,函數的作用就是将定義域映射到值域的操作。這裡T就是定義域,R是值域。映射關系交給具體實作去定義。
1 public interface Function 
2 {
3    public R apply(T t);
4 }      

示例程式

1 // A simple program to demonstrate the use 
 2 // of predicate interface 
 3 import java.util.*; 
 4 import java.util.function.Predicate; 
 5   
 6 class Test 
 7 { 
 8     public static void main(String args[]) 
 9     { 
10   
11         // create a list of strings 
12         List<String> names = 
13             Arrays.asList("Geek","GeeksQuiz","g1","QA","Geek2"); 
14   
15         // declare the predicate type as string and use 
16         // lambda expression to create object 
17         Predicate<String> p = (s)->s.startsWith("G"); 
18   
19         // Iterate through the list 
20         for (String st:names) 
21         { 
22             // call the test method 
23             if (p.test(st)) 
24                 System.out.println(st); 
25         } 
26     } 
27 }      

 supplier與consumer

以下轉自Java8之函數式程式設計Supplier接口和Consumer接口

  • Supplier : 顧名思義,這是一個供應商,提供者.就如一個工廠一樣.該類的源碼如下:
1 package java.util.function;
2  
3 @FunctionalInterface
4 public interface Supplier<T> {
5  
6     T get();
7 }      
  • Consumer : 顧名思義,這是一個消費者,該類的源碼如下:
1 package java.util.function;
 2  
 3 import java.util.Objects;
 4  
 5 @FunctionalInterface
 6 public interface Consumer<T> {
 7  
 8     void accept(T t);
 9  
10     default Consumer<T> andThen(Consumer<? super T> after) {
11         Objects.requireNonNull(after);
12         return (T t) -> { accept(t); after.accept(t); };
13     }
14 }      

accept方法

        該函數式接口的唯一的抽象方法,接收一個參數,沒有傳回值.

andThen方法

        在執行完調用者方法後再執行傳入參數的方法.

 執行個體

1 import java.util.function.Consumer;
 2  
 3 public class ConsumerTest {
 4     public static void main(String[] args) {
 5         Consumer<Integer> consumer = (x) -> {
 6             int num = x * 2;
 7             System.out.println(num);
 8         };
 9         Consumer<Integer> consumer1 = (x) -> {
10             int num = x * 3;
11             System.out.println(num);
12         };
13         consumer.andThen(consumer1).accept(10);
14     }
15 }      

輸出結果為:

可以看出先将10傳入consumer方法執行,然後再将10傳入consumer2方法執行.

使用 supplier實作資源懶加載

JDK 1.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

JDK 1.8 新增加的函數接口

  • java.util.function

java.util.function 它包含了很多類,用來支援 Java的 函數式程式設計,該包中的函數式接口有:

序号 接口 & 描述
1

BiConsumer<T,U>

代表了一個接受兩個輸入參數的操作,并且不傳回任何結果

2

BiFunction<T,U,R>

代表了一個接受兩個輸入參數的方法,并且傳回一個結果

3

BinaryOperator<T>

代表了一個作用于于兩個同類型操作符的操作,并且傳回了操作符同類型的結果

4

BiPredicate<T,U>

代表了一個兩個參數的boolean值方法

5

BooleanSupplier

代表了boolean值結果的提供方

6

Consumer<T>

代表了接受一個輸入參數并且無傳回的操作

7

DoubleBinaryOperator

代表了作用于兩個double值操作符的操作,并且傳回了一個double值的結果。

8

DoubleConsumer

代表一個接受double值參數的操作,并且不傳回結果。

9

DoubleFunction<R>

代表接受一個double值參數的方法,并且傳回結果

10

DoublePredicate

代表一個擁有double值參數的boolean值方法

11

DoubleSupplier

代表一個double值結構的提供方

12

DoubleToIntFunction

接受一個double類型輸入,傳回一個int類型結果。

13

DoubleToLongFunction

接受一個double類型輸入,傳回一個long類型結果

14

DoubleUnaryOperator

接受一個參數同為類型double,傳回值類型也為double 。

15

Function<T,R>

接受一個輸入參數,傳回一個結果。

16

IntBinaryOperator

接受兩個參數同為類型int,傳回值類型也為int 。

17

IntConsumer

接受一個int類型的輸入參數,無傳回值 。

18

IntFunction<R>

接受一個int類型輸入參數,傳回一個結果 。

19

IntPredicate

:接受一個int輸入參數,傳回一個布爾值的結果。

20

IntSupplier

無參數,傳回一個int類型結果。

21

IntToDoubleFunction

接受一個int類型輸入,傳回一個double類型結果 。

22

IntToLongFunction

接受一個int類型輸入,傳回一個long類型結果。

23

IntUnaryOperator

接受一個參數同為類型int,傳回值類型也為int 。

24

LongBinaryOperator

接受兩個參數同為類型long,傳回值類型也為long。

25

LongConsumer

接受一個long類型的輸入參數,無傳回值。

26

LongFunction<R>

接受一個long類型輸入參數,傳回一個結果。

27

LongPredicate

R接受一個long輸入參數,傳回一個布爾值類型結果。

28

LongSupplier

無參數,傳回一個結果long類型的值。

29

LongToDoubleFunction

接受一個long類型輸入,傳回一個double類型結果。

30

LongToIntFunction

接受一個long類型輸入,傳回一個int類型結果。

31

LongUnaryOperator

接受一個參數同為類型long,傳回值類型也為long。

32

ObjDoubleConsumer<T>

接受一個object類型和一個double類型的輸入參數,無傳回值。

33

ObjIntConsumer<T>

接受一個object類型和一個int類型的輸入參數,無傳回值。

34

ObjLongConsumer<T>

接受一個object類型和一個long類型的輸入參數,無傳回值。

35

Predicate<T>

接受一個輸入參數,傳回一個布爾值結果。

36

Supplier<T>

無參數,傳回一個結果。

37

ToDoubleBiFunction<T,U>

接受兩個輸入參數,傳回一個double類型結果

38

ToDoubleFunction<T>

接受一個輸入參數,傳回一個double類型結果

39

ToIntBiFunction<T,U>

接受兩個輸入參數,傳回一個int類型結果。

40

ToIntFunction<T>

接受一個輸入參數,傳回一個int類型結果。

41

ToLongBiFunction<T,U>

接受兩個輸入參數,傳回一個long類型結果。

42

ToLongFunction<T>

接受一個輸入參數,傳回一個long類型結果。

43

UnaryOperator<T>

接受一個參數為類型T,傳回值類型也為T。

TALK IS CHEAP, SHOW ME THE CODE