成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Java如何支持函數式編程?

開發 后端
Java是面向對象的語言,無法直接調用一個函數。Java 8開始,引入了函數式編程接口與Lambda表達式,便于開發者寫出更少更優雅的代碼。什么是函數式編程?函數式編程的特點是什么?本文通過代碼實例,從Stream類、Lambda表達式和函數接口這三個語法概念來分享Java對函數式編程的支持。

 背景

[[343226]]

在很長的一段時間里,Java一直是面向對象的語言,一切皆對象,如果想要調用一個函數,函數必須屬于一個類或對象,然后在使用類或對象進行調用。但是在其它的編程語言中,如JS、C++,我們可以直接寫一個函數,然后在需要的時候進行調用,既可以說是面向對象編程,也可以說是函數式編程。從功能上來看,面向對象編程沒什么不好的地方,但是從開發的角度來看,面向對象編程會多寫很多可能是重復的代碼行。比如創建一個Runnable的匿名類的時候:

 

  1. Runnable runnable = new Runnable() { 
  2.     @Override 
  3.     public void run() { 
  4.         System.out.println("do something..."); 
  5.     } 
  6. }; 

這一段代碼中真正有用的只有run方法中的內容,剩余的部分都是屬于Java編程語言的結構部分,沒什么用,但是要寫。幸運的是Java 8開始,引入了函數式編程接口與Lambda表達式,幫助我們寫更少更優雅的代碼:

 

  1. // 一行即可 
  2. Runnable runnable = () -> System.out.println("do something..."); 

現在主流的編程范式主要有三種,面向過程、面向對象和函數式編程。

函數式編程并非一個很新的東西,早在50多年前就已經出現了。近幾年,函數式編程越來越被人關注,出現了很多新的函數式編程語言,比如Clojure、Scala、Erlang等。一些非函數式編程語言也加入了很多特性、語法、類庫來支持函數式編程,比如Java、Python、Ruby、JavaScript等。除此之外,Google Guava也有對函數式編程的增強功能。

函數式編程因其編程的特殊性,僅在科學計算、數據處理、統計分析等領域,才能更好地發揮它的優勢,所以它并不能完全替代更加通用的面向對象編程范式。但是作為一種補充,它也有很大存在、發展和學習的意義。

什么是函數式編程

函數式編程的英文翻譯是Functional Programming。

那到底什么是函數式編程呢?實際上,函數式編程沒有一個嚴格的官方定義。嚴格上來講,函數式編程中的“函數”,并不是指我們編程語言中的“函數”概念,而是指數學“函數”或者“表達式”(例如:y=f(x))。不過,在編程實現的時候,對于數學“函數”或“表達式”,我們一般習慣性地將它們設計成函數。所以,如果不深究的話,函數式編程中的“函數”也可以理解為編程語言中的“函數”。

每個編程范式都有自己獨特的地方,這就是它們會被抽象出來作為一種范式的原因。面向對象編程最大的特點是:以類、對象作為組織代碼的單元以及它的四大特性。面向過程編程最大的特點是:以函數作為組織代碼的單元,數據與方法相分離。那函數式編程最獨特的地方又在哪里呢?實際上,函數式編程最獨特的地方在于它的編程思想。函數式編程認為程序可以用一系列數學函數或表達式的組合來表示。函數式編程是程序面向數學的更底層的抽象,將計算過程描述為表達式。不過,這樣說你肯定會有疑問,真的可以把任何程序都表示成一組數學表達式嗎?

理論上講是可以的。但是,并不是所有的程序都適合這么做。函數式編程有它自己適合的應用場景,比如科學計算、數據處理、統計分析等。在這些領域,程序往往比較容易用數學表達式來表示,比起非函數式編程,實現同樣的功能,函數式編程可以用很少的代碼就能搞定。但是,對于強業務相關的大型業務系統開發來說,費勁吧啦地將它抽象成數學表達式,硬要用函數式編程來實現,顯然是自討苦吃。相反,在這種應用場景下,面向對象編程更加合適,寫出來的代碼更加可讀、可維護。

再具體到編程實現,函數式編程跟面向過程編程一樣,也是以函數作為組織代碼的單元。不過,它跟面向過程編程的區別在于,它的函數是無狀態的。何為無狀態?簡單點講就是,函數內部涉及的變量都是局部變量,不會像面向對象編程那樣,共享類成員變量,也不會像面向過程編程那樣,共享全局變量。函數的執行結果只與入參有關,跟其他任何外部變量無關。同樣的入參,不管怎么執行,得到的結果都是一樣的。這實際上就是數學函數或數學表達式的基本要求。舉個例子:

 

  1. // 有狀態函數: 執行結果依賴b的值是多少,即便入參相同, 
  2. // 多次執行函數,函數的返回值有可能不同,因為b值有可能不同。 
  3. int b; 
  4. int increase(int a) { 
  5.   return a + b; 
  6.  
  7. // 無狀態函數:執行結果不依賴任何外部變量值 
  8. // 只要入參相同,不管執行多少次,函數的返回值就相同 
  9. int increase(int a, int b) { 
  10.   return a + b; 

不同的編程范式之間并不是截然不同的,總是有一些相同的編程規則。比如不管是面向過程、面向對象還是函數式編程,它們都有變量、函數的概念,最頂層都要有main函數執行入口,來組裝編程單元(類、函數等)。只不過,面向對象的編程單元是類或對象,面向過程的編程單元是函數,函數式編程的編程單元是無狀態函數。

Java對函數式編程的支持

實現面向對象編程不一定非得使用面向對象編程語言,同理,實現函數式編程也不一定非得使用函數式編程語言。現在,很多面向對象編程語言,也提供了相應的語法、類庫來支持函數式編程。

Java這種面向對象編程語言,對函數式編程的支持可以通過一個例子來描述:

 

  1. public class Demo { 
  2.   public static void main(String[] args) { 
  3.     Optional<Integer> result = Stream.of("a""be""hello"
  4.             .map(s -> s.length()) 
  5.             .filter(l -> l <= 3) 
  6.             .max((o1, o2) -> o1-o2); 
  7.     System.out.println(result.get()); // 輸出2 
  8.   } 

這段代碼的作用是從一組字符串數組中,過濾出長度小于等于3的字符串,并且求得這其中的最大長度。

Java為函數式編程引入了三個新的語法概念:Stream類、Lambda表達式和函數接口(Functional Inteface)。Stream類用來支持通過“.”級聯多個函數操作的代碼編寫方式;引入Lambda表達式的作用是簡化代碼編寫;函數接口的作用是讓我們可以把函數包裹成函數接口,來實現把函數當做參數一樣來使用(Java 不像C那樣支持函數指針,可以把函數直接當參數來使用)。

Stream類

假設我們要計算這樣一個表達式:(3-1)*2+5。如果按照普通的函數調用的方式寫出來,就是下面這個樣子:

 

  1. add(multiply(subtract(3,1),2),5); 

不過,這樣編寫代碼看起來會比較難理解,我們換個更易讀的寫法,如下所示:

 

  1. subtract(3,1).multiply(2).add(5); 

在Java中,“.”表示調用某個對象的方法。為了支持上面這種級聯調用方式,我們讓每個函數都返回一個通用的Stream類對象。在Stream類上的操作有兩種:中間操作和終止操作。中間操作返回的仍然是Stream類對象,而終止操作返回的是確定的值結果。

再來看之前的例子,對代碼做了注釋解釋。其中map、filter是中間操作,返回Stream類對象,可以繼續級聯其他操作;max是終止操作,返回的不是Stream類對象,無法再繼續往下級聯處理了。

 

  1. public class Demo { 
  2.   public static void main(String[] args) { 
  3.     Optional<Integer> result = Stream.of("f""ba""hello") // of返回Stream<String>對象 
  4.             .map(s -> s.length()) // map返回Stream<Integer>對象 
  5.             .filter(l -> l <= 3) // filter返回Stream<Integer>對象 
  6.             .max((o1, o2) -> o1-o2); // max終止操作:返回Optional<Integer
  7.     System.out.println(result.get()); // 輸出2 
  8.   } 

Lambda表達式

前面提到Java引入Lambda表達式的主要作用是簡化代碼編寫。實際上,我們也可以不用Lambda表達式來書寫例子中的代碼。我們拿其中的map函數來舉例說明。

下面三段代碼,第一段代碼展示了map函數的定義,實際上,map函數接收的參數是一個Function接口,也就是函數接口。第二段代碼展示了map函數的使用方式。第三段代碼是針對第二段代碼用Lambda表達式簡化之后的寫法。實際上,Lambda表達式在Java中只是一個語法糖而已,底層是基于函數接口來實現的,也就是第二段代碼展示的寫法。

 

  1. // Stream類中map函數的定義: 
  2. public interface Stream<T> extends BaseStream<T, Stream<T>> { 
  3.   <R> Stream<R> map(Function<? super T, ? extends R> mapper); 
  4.   //...省略其他函數... 
  5.  
  6. // Stream類中map的使用方法示例: 
  7. Stream.of("fo""bar""hello").map(new Function<String, Integer>() { 
  8.   @Override 
  9.   public Integer apply(String s) { 
  10.     return s.length(); 
  11.   } 
  12. }); 
  13.  
  14. // 用Lambda表達式簡化后的寫法: 
  15. Stream.of("fo""bar""hello").map(s -> s.length()); 

Lambda表達式包括三部分:輸入、函數體、輸出。表示出來的話就是下面這個樣子:

 

  1. (a, b) -> { 語句1;語句2;...; return 輸出; } //a,b是輸入參數 

實際上,Lambda表達式的寫法非常靈活。上面給出的是標準寫法,還有很多簡化寫法。比如,如果輸入參數只有一個,可以省略 (),直接寫成 a->{…};如果沒有入參,可以直接將輸入和箭頭都省略掉,只保留函數體;如果函數體只有一個語句,那可以將{}省略掉;如果函數沒有返回值,return語句就可以不用寫了。

 

  1. Optional<Integer> result = Stream.of("f""ba""hello"
  2.         .map(s -> s.length()) 
  3.         .filter(l -> l <= 3) 
  4.         .max((o1, o2) -> o1-o2); 
  5.          
  6. // 還原為函數接口的實現方式 
  7. Optional<Integer> result2 = Stream.of("fo""bar""hello"
  8.         .map(new Function<String, Integer>() { 
  9.           @Override 
  10.           public Integer apply(String s) { 
  11.             return s.length(); 
  12.           } 
  13.         }) 
  14.         .filter(new Predicate<Integer>() { 
  15.           @Override 
  16.           public boolean test(Integer l) { 
  17.             return l <= 3; 
  18.           } 
  19.         }) 
  20.         .max(new Comparator<Integer>() { 
  21.           @Override 
  22.           public int compare(Integer o1, Integer o2) { 
  23.             return o1 - o2; 
  24.           } 
  25.         }); 

Lambda表達式與匿名類的異同集中體現在以下三點上:

  • Lambda就是為了優化匿名內部類而生,Lambda要比匿名類簡潔的多得多。
  • Lambda僅適用于函數式接口,匿名類不受限。
  • 即匿名類中的this是“匿名類對象”本身;Lambda表達式中的this是指“調用Lambda表達式的對象”。

函數接口

實際上,上面一段代碼中的Function、Predicate、Comparator都是函數接口。我們知道,C語言支持函數指針,它可以把函數直接當變量來使用。

但是,Java沒有函數指針這樣的語法。所以它通過函數接口,將函數包裹在接口中,當作變量來使用。實際上,函數接口就是接口。不過,它也有自己特別的地方,那就是要求只包含一個未實現的方法。因為只有這樣,Lambda表達式才能明確知道匹配的是哪個方法。如果有兩個未實現的方法,并且接口入參、返回值都一樣,那Java在翻譯Lambda表達式的時候,就不知道表達式對應哪個方法了。

函數式接口也是Java interface的一種,但還需要滿足:

  • 一個函數式接口只有一個抽象方法(single abstract method);
  • Object類中的public abstract method不會被視為單一的抽象方法;
  • 函數式接口可以有默認方法和靜態方法;
  • 函數式接口可以用@FunctionalInterface注解進行修飾。

滿足這些條件的interface,就可以被視為函數式接口。例如Java 8中的Comparator接口:

 

  1. @FunctionalInterface 
  2. public interface Comparator<T> { 
  3.     /** 
  4.      * single abstract method 
  5.      * @since 1.8 
  6.      */ 
  7.     int compare(T o1, T o2); 
  8.  
  9.     /** 
  10.      * Object類中的public abstract method  
  11.      * @since 1.8 
  12.      */ 
  13.     boolean equals(Object obj); 
  14.  
  15.     /** 
  16.      * 默認方法 
  17.      * @since 1.8 
  18.      */ 
  19.     default Comparator<T> reversed() { 
  20.         return Collections.reverseOrder(this); 
  21.     } 
  22.  
  23.      
  24.     /** 
  25.      * 靜態方法 
  26.      * @since 1.8 
  27.      */ 
  28.     public static <T extends Comparable<? super T>> Comparator<T> reverseOrder() { 
  29.         return Collections.reverseOrder(); 
  30.     } 
  31.  
  32.     //省略... 

函數式接口有什么用呢?一句話,函數式接口帶給我們最大的好處就是:可以使用極簡的lambda表達式實例化接口。為什么這么說呢?我們或多或少使用過一些只有一個抽象方法的接口,比如Runnable、ActionListener、Comparator等等,比如我們要用Comparator實現排序算法,我們的處理方式通常無外乎兩種:

  • 規規矩矩的寫一個實現了Comparator接口的Java類去封裝排序邏輯。若業務需要多種排序方式,那就得寫多個類提供多種實現,而這些實現往往只需使用一次。
  • 另外一種聰明一些的做法無外乎就是在需要的地方搞個匿名內部類,比如:

 

  1. public class Test {  
  2.     public static void main(String args[]) {  
  3.         List<Person> persons = new ArrayList<Person>(); 
  4.         Collections.sort(persons, new Comparator<Person>(){ 
  5.             @Override 
  6.             public int compare(Person o1, Person o2) { 
  7.                 return Integer.compareTo(o1.getAge(), o2.getAge()); 
  8.             } 
  9.         }); 
  10.     }  

匿名內部類實現的代碼量沒有多到哪里去,結構也還算清晰。Comparator接口在Jdk 1.8的實現增加了FunctionalInterface注解,代表Comparator是一個函數式接口,使用者可放心的通過lambda表達式來實例化。那我們來看看使用lambda表達式來快速new一個自定義比較器所需要編寫的代碼:

 

  1. Comparator<Person> comparator = (p1, p2) -> Integer.compareTo(p1.getAge(), p2.getAge()); 

-> 前面的 () 是Comparator接口中compare方法的參數列表,-> 后面則是compare方法的方法體。

下面將Java提供的Function、Predicate這兩個函數接口的源碼,摘抄如下:

 

  1. @FunctionalInterface 
  2. public interface Function<T, R> { 
  3.     R apply(T t);  // 只有這一個未實現的方法 
  4.  
  5.     default <V> Function<V, R> compose(Function<? super V, ? extends T> before) { 
  6.         Objects.requireNonNull(before); 
  7.         return (V v) -> apply(before.apply(v)); 
  8.     } 
  9.  
  10.     default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) { 
  11.         Objects.requireNonNull(after); 
  12.         return (T t) -> after.apply(apply(t)); 
  13.     } 
  14.  
  15.     static <T> Function<T, T> identity() { 
  16.         return t -> t; 
  17.     } 
  18.  
  19. @FunctionalInterface 
  20. public interface Predicate<T> { 
  21.     boolean test(T t); // 只有這一個未實現的方法 
  22.  
  23.     default Predicate<T> and(Predicate<? super T> other) { 
  24.         Objects.requireNonNull(other); 
  25.         return (t) -> test(t) && other.test(t); 
  26.     } 
  27.  
  28.     default Predicate<T> negate() { 
  29.         return (t) -> !test(t); 
  30.     } 
  31.  
  32.     default Predicate<T> or(Predicate<? super T> other) { 
  33.         Objects.requireNonNull(other); 
  34.         return (t) -> test(t) || other.test(t); 
  35.     } 
  36.  
  37.     static <T> Predicate<T> isEqual(Object targetRef) { 
  38.         return (null == targetRef) 
  39.                 ? Objects::isNull 
  40.                 : object -> targetRef.equals(object); 
  41.     } 

@FunctionalInterface注解使用場景

我們知道,一個接口只要滿足只有一個抽象方法的條件,即可以當成函數式接口使用,有沒有 @FunctionalInterface 都無所謂。但是jdk定義了這個注解肯定是有原因的,對于開發者,該注解的使用一定要三思而后續行。

如果使用了此注解,再往接口中新增抽象方法,編譯器就會報錯,編譯不通過。換句話說,@FunctionalInterface 就是一個承諾,承諾該接口世世代代都只會存在這一個抽象方法。因此,凡是使用了這個注解的接口,開發者可放心大膽的使用Lambda來實例化。當然誤用 @FunctionalInterface 帶來的后果也是極其慘重的:如果哪天你把這個注解去掉,再加一個抽象方法,則所有使用Lambda實例化該接口的客戶端代碼將全部編譯錯誤。

特別地,當某接口只有一個抽象方法,但沒有用 @FunctionalInterface 注解修飾時,則代表別人沒有承諾該接口未來不增加抽象方法,所以建議不要用Lambda來實例化,還是老老實實的用以前的方式比較穩妥。

小結

函數式編程更符合數學上函數映射的思想。具體到編程語言層面,我們可以使用Lambda表達式來快速編寫函數映射,函數之間通過鏈式調用連接到一起,完成所需業務邏輯。Java的Lambda表達式是后來才引入的,由于函數式編程在并行處理方面的優勢,正在被大量應用在大數據計算領域。

責任編輯:華軒 來源: 阿里技術
相關推薦

2020-09-23 07:50:45

Java函數式編程

2011-08-24 09:13:40

編程

2013-09-09 09:41:34

2024-02-28 08:37:28

Lambda表達式Java函數式接口

2023-12-14 15:31:43

函數式編程python編程

2022-09-22 08:19:26

WebFlux函數式編程

2016-10-31 20:46:22

函數式編程Javascript

2011-03-08 15:47:32

函數式編程

2020-09-24 10:57:12

編程函數式前端

2025-03-11 10:00:20

Golang編程函數

2017-06-08 14:25:46

Kotlin函數

2010-11-25 09:06:37

Web開發函數式編程

2023-10-07 00:01:02

Java函數

2012-08-21 09:20:37

函數式編程函數編程編程

2016-08-11 10:11:07

JavaScript函數編程

2016-08-11 10:34:37

Javascript函數編程

2010-03-11 10:34:22

Scala

2012-09-21 09:21:44

函數式編程函數式語言編程

2015-09-28 14:54:08

Java函數式編程

2019-09-09 11:40:18

編程函數開發
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 久久男女视频 | 日本成年免费网站 | 日韩精品免费一区二区在线观看 | 一级免费毛片 | 91精品国产综合久久婷婷香蕉 | 另类视频区| 亚洲另类春色偷拍在线观看 | 97精品超碰一区二区三区 | 国产伊人久久久 | 久久精品黄色 | 日韩1区 | 欧美一级免费 | 亚洲网一区 | 性高湖久久久久久久久3小时 | 国产在线a| 久久午夜影院 | 一区二区在线免费观看 | 欧美男人天堂 | 日韩视频在线观看 | 中文字幕在线一区二区三区 | 日本精品视频一区二区三区四区 | 免费一级欧美在线观看视频 | 在线看亚洲 | 午夜天堂精品久久久久 | 久久999| 欧美成人激情 | 在线日韩欧美 | 久久精品一级 | 色综合99 | 91久久北条麻妃一区二区三区 | 色婷婷亚洲| 欧美日韩在线免费观看 | 欧美日韩在线成人 | avhd101在线成人播放 | 亚洲精品v| 91精品亚洲 | 成人免费视频 | 羞羞色在线观看 | 久久成人国产精品 | 不卡的av一区 | 中文字幕欧美一区二区 |