Java8中Lambda表達式的理解和運用

目錄:java

1、簡介程序員

2、函數接口編程

3、Lambda表達式api

4、目標類型數組

5、流閉包

6、function包併發

7、對併發的影響app

正文函數式編程

1、簡介函數

一、java中lambda的由來

開發類庫的程序員使用java時,發現抽象級別還不夠,尤爲是面對大型數據集合時,java還欠缺高效的並行操做。爲了編寫這類處理批量數據的並行類庫,就須要在爲java增長lambda表達式。

再如,當咱們定義一個線程類時,能夠將該類實現Runnable接口(該接口只有一個方法run()),並實現run方法便可。但大多數狀況下咱們可能並不這麼作,由於該線程可能只會被使用一次,此時咱們通常會使用匿名內部類將線程的行爲進行內聯,代碼舉例以下:

可是匿名內部類存在缺陷:

  1. 語法過於冗餘
  2. 匿名內部類中的this和變量名容易令人產生誤解
  3. 沒法捕獲非final的局部變量

等等,因爲上述缺陷,須要藉助函數式編程得以解決。

2、函數式接口

咱們對面向對象編程並不陌生,面向對象編程是對數據進行抽象,而函數式編程是對行爲進行抽象。現實世界中,數據和行爲是並存的。

那麼什麼是函數式編程呢?其核心思想是:在思考問題時,使用不可變值和函數,函數對一個值進行處理,映射成另外一個值。

函數式接口定義:把只有一個抽象方法的接口稱爲函數式接口,可用作lambda表達式的類型。java中的函數式接口如Runnable、Comparator、Callable等。

如何自定義一個函數式接口?其實咱們並須要額外的工做來聲明一個接口是函數式接口,編譯器會根據接口的結構自行判斷,固然並不是簡單對接口中的方法進行計數。另外,java api提供了@FunctionalInterface註解來顯示聲明一個接口爲函數式接口,加上該註解後,編譯器就會驗證該接口是否知足函數式接口的要求。

java8中增長了一個新的package:java.util.function,裏面包含了經常使用的函數式接口。

3、Lambda表達式

lambda表達式又被稱爲閉包或匿名方法。

下面對lambda進行一些簡單舉例:

  1. (int x, int y) -> x + y    表示:接收整形參數x和y,並返回他們的和
  2. () -> 66                        表示:沒有參數傳入,直接返回數字66
  3. (String s) -> { System.out.println(s); }    表示:接收一個字符串型的參數,並將它輸出到控制檯,不返回值

因而可知,lambda表達式由參數列表、箭頭符號(->)和函數體組成,其中函數體既能夠是一個表達式,也能夠是一個語句塊。表達式函數體適用於小型lambda表達式,它省略了return關鍵字,使語法更簡潔。

在使用lambda表達式時,能夠顯示聲明參數的類型,如:(int x, int y) -> x + y,其實也能夠省略參數類型,讓編譯器本身去推導出來,如:(x, y) -> x + y。

4、目標類型

一、目標類型

編譯器負責推導lambda表達式的類型,它利用lambda表達式所在上下文所期待的類型進行推導,這個被期待的類型就是目標類型。lambda只能出如今目標類型爲函數接口的上下文中。

lambda表達式對目標類型也是有要求的,當下面全部條件都成立時,lambda表達式纔會賦給目標類型T。

  • T是一個函數式接口
  • lambda表達式的參數和T的方法參數在數量和類型上一一對應
  • lambda表達式的返回值和T的方法返回值相兼容
  • lambda表達式內所拋出的異常和T方法的throws類型相兼容

二、目標類型的上下文包括:

  • 變量聲明:Callable<Integer> callable;
  • 賦值       :  callable = () -> "hello";
  • 返回語句 : public Runnable runner() { return () -> { System.out.println("hello"); }; }
  • 數組初始化器 : FileFilter[] fileFilters = new FileFilter[] { f -> f.exists(), f -> f.canRead(), f -> f.getName().startsWith("q") };
  • 方法或構造方法的參數 :
    方法或構造方法參數對目標類型的確認會涉及到其它兩個語言特性:重載解析和參數類型推導。若是lambda具備顯示類型(參數類型被顯示指定),編譯器就能夠直接使用lambda表達式的返回類型;若是lambda表達式具備隱式類型(參數類型被推導而知),重載解析則會忽略lambda表達式函數體而只依賴lambda表達式參數的數量。若是在解析方法聲明時存在二義性,咱們就須要利用轉型或顯示提供lambda表達式類型。舉例:
  • lambda表達式函數體  : Supplier<Runnable> supplier = () -> () -> { System.out.println("hi"); };
  • 條件表達式(? :): Callable<Integer> c = true ? (() -> 23) : (() -> 42);
  • 轉型表達式(cast): Runnable o = (Runnable) () -> { System.out.println("hi"); };

三、詞法做用域

lambda引用的是值,而不是變量。在匿名內部類中引用它所在方法的變量時,該變量必須聲明爲final,雖然Java8中放寬了此限制,能夠引用非final變量,可是該變量在既成事實上必須是final的,即不能再匿名內部類中改變它所在方法的變量值,不然編譯器會報錯。既成事實上的final是指只能給該變量賦值一次,換句話說,Lambda引用的是值,而不是變量。舉例以下:

       

上面兩個例子中,只要在內部類或lambda表達式中修改其所在方法中的變量都會報錯。lambda表達式不支持修改外部變量的另外一個緣由是:咱們可使用更好的方式實現相同的效果:使用規約。java.util.function包中提供了各類規約,如sum、min、max等。

在內部類中定義和外部類中相同的變量時,其內部類中定義的變量會覆蓋外部類中定義的變量。而lambda表達式基於詞法做用域,在lambda函數體裏面不容許定義和外面相同的變量,若是定義則會報錯。舉例:

所以,能夠說:lambda表達式對值封閉,對變量開放。

5、流

一、概念

外部迭代:經過Iterator的方式進行迭代的過程稱爲外部迭代。

內部迭代:經過stream的方式進行迭代的過程稱爲內部迭代。

流 - Stream:是用函數式編程的方式在集合類上進行復雜操做的工具。java8中爲集合類和數組都提供了轉爲Stream的方法,由集合或數組生成的Stream不是一個新集合,而是建立新集合的配方。

惰性求值方法:只描述或刻畫Stream,最終不產生新集合的方法叫惰性求值方法,如Stream的filter方法。

及早求值方法:最終會生成新集合的方法叫及早求值方法,如Stream的count方法。

說明:判斷一個方法是惰性求值仍是及早求值很簡單:只需看它的返回值,若是返回值是Stream,那麼就是惰性求值方法,若是返回的是另外一個值或空,就是及早求值方法。使用這些操做的理想方式就是造成一個惰性求值的鏈,最後用一個及早求值的操做返回想要的結果。

二、經常使用的流操做

2.1 collect(Collector<? super T,A,R>)

做用:該方法由Stream裏的值生成一個列表,是一個及早求值方法。

舉例:

List<String> list = Stream.of("a", "1", "b", "2").collect(Collectors.toList());

2.2 Stream<R> map(Function<? super T, ? super R>)

做用:將一個流中的值轉換成一個新流,是一個惰性求值方法。

舉例:

Stream<String> stream = Stream.of("a","b","c").map(one -> one.toUpperCase());

2.3 Stream<T> filter(Predicate<? super T>)

做用:遍歷流中的數據並根據條件進行過濾,造成一個新流,是一個惰性求值方法。

舉例:

Stream<String> stream1 = Stream.of("abc","bcd","def").filter(one -> one.contains("bc"));

2.4 Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>>)

做用:當流中包含多個列表時,可將這多個列表中的內容打平放到一個列表下的流中,是一個惰性求值方法。

舉例:

Stream<String> stream2 = Stream.of(Arrays.asList("a","b","c"), Arrays.asList("abc","bcd","def")).flatMap(one -> one.stream());

2.5 Optional<T> max(Comparator<? super T>)和Optional<T> min(Comparator<? super T>)

做用:獲取一個集合中的最大值和最小值,是兩個及早求值方法

舉例:

Integer max = Stream.of(1,2,3).max(Comparator.comparing(one->one)).get();
Integer min = Stream.of(1,2,3).min(Comparator.comparing(one->one)).get();

2.6 long count()

做用:返回Stream中元素的數量,是一個及早求值方法

舉例:

Long count = Stream.of(1,2,3).count();

2.7 Optional<T> reduce(BinaryOperator<T>)

做用:從stream的一組值中生成1個值,count、max、min都屬於reduce操做,是一個及早求值方法。

舉例:第1個參數one是上次函數計算的返回值,其初始值是stream中第1個值;第2個參數two是stream中的元素值,其初始值是stream中第2個值,返回類型是Optional,經過get方法獲取其值

Integer count = Stream.of(1,2,3,4,5,6).reduce((one, two) -> { System.out.println(one + ":"+two);return two; }).get();

2.8 T reduce(T, BinaryOperator<T>)

舉例:該方法比上一個方法多了一個初始值100,one仍然表明上次函數計算的返回值,其初始值爲就是設定的初始值100;two仍然是stream中的元素值,其初始值是stream中的第1個值。因爲該方法指定了初始值,所以該方法的返回值類型就是設定的初始值類型,即Integer。

Integer count1 = Stream.of(1,2,3,4,5,6).reduce(100, (one, two) -> { System.out.println(one + ":"+two);return two; });

2.9 Stream<T> distinct()

做用:返回由該流的不一樣元素組成的一個新流,是一個惰性求值方法

舉例:

Stream.of(3,2,5,3,4,3).distinct().peek(one -> System.out.println(one)).count();

2.10 Stream<T> limit(int maxSize)

做用:返回前maxSize個元素組成的新流,是一個惰性求值方法

舉例:

System.out.println(Stream.of(1,2,3,4,5,6).limit(3).filter(one -> {System.out.println(one);return true;}).count());

2.11 Stream<T> peek(Consumer<? super T>)

做用:返回一個由該流的元素組成的新流,而且對新流中每一個元素執行指定的操做

舉例:

Stream stream = Stream.of(1,2,3,4,5,6).peek(one -> System.out.println(one));

2.12 Stream<T> skip(long n)

做用:從流的第1個元素開始,跳過n個元素,把從第n+1個元素開始到最後全部的元素造成一個新流

舉例:

Stream.of(1,2,3,4,5,6).skip(3).peek(one -> System.out.println(one)).count();

2.13 Stream<T> sorted()

做用:將流中的元素按照天然順序進行排序,是一個惰性求值方法

舉例:

Stream.of(6,2,5,3,4,1).peek(one-> System.out.println(one)).sorted().peek(one-> System.out.println(one)).count();

2.14 boolean allMatch(Predicate<? super T>)

做用:返回流中的全部元素是否都與條件匹配

舉例:

System.out.println(Stream.of(6,2,5,3,4,1).allMatch(one -> one>3));

2.15 boolean anyMatch(Predicate<? super T>)

做用:返回流中是否存在與條件匹配的元素

舉例:

System.out.println(Stream.of(6,2,5,3,4,1).anyMatch(one -> one>3));

2.16 boolean noneMatch(Predicate<? super T>)

做用:返回流中全部元素是否都不與條件匹配

舉例:

System.out.println(Stream.of(6,2,5,3,4,1).noneMatch(one -> one>3));

2.17 findAny()

做用:返回描述流的一些元素的Optional對象,若是流爲空,則返回一個空Optional

舉例:

System.out.println(Stream.of(3,2,5,6,4,1).findAny().get());

2.18 findFirst()

做用:返回描述流的第一個元素的Optional對象,若是流爲空,則返回一個空Optional

舉例:

System.out.println(Stream.of(3,2,5,6,4,1).findAny().get());

2.19 void forEach(Consumer<? super T>)

做用:對流中每一個元素循環進行操做

舉例:

Stream.of(3,2,5,6,4,1).forEach(one -> System.out.println(one));

2.20 Object[] toArray()

做用:返回一個包含流中元素的數組

舉例:

Integer[] values = (Integer[])Stream.of(3,2,5,6,4,1).toArray();

6、function包

一、Predicate<T>

抽象方法爲boolean test(T),傳入T類型的參數,返回一個boolean類型值。可用於流中數值判斷。

舉例:

Predicate<String> predicate = one -> one.length() > 0;

二、Consumer<T>

抽象方法爲void accept(T),傳入T類型的參數,不返回值。可用於實現消費者

舉例:

Consumer<Integer> consumer = one -> System.out.println(one);

三、Supplier<T>

抽象方法爲String[] value(),無參數,返回一個字符串數組。可用於實現生產者

舉例:

Supplier<Integer> supplier = () -> new Integer(100);

四、Function<T, R>

抽象方法爲R apply(T),傳入參數T,返回結果R。

舉例:

Function<String, Integer> function = one -> one.length();

五、UnaryOperator<T>

抽象方法T apply(T),接收T對象,返回T對象。

舉例:

UnaryOperator<String> unaryOperator = one -> "hello:" + one;

六、BinaryOperator<T>

抽象方法爲T apply(T, T),接收2個T對象,返回T對象。

舉例:

BinaryOperator<Integer> binaryOperator = (one, two) -> one + two;

7、對併發的影響

待續

相關文章
相關標籤/搜索