Java8新特性-函數式編程(Stream/Function/Optional/Consumer)

Java8新引入函數式編程方式,大大的提升了編碼效率。本文將對涉及的對象等進行統一的學習及記錄。java

首先須要清楚一個概念:函數式接口;它指的是有且只有一個未實現的方法的接口,通常經過FunctionalInterface這個註解來代表某個接口是一個函數式接口。函數式接口是Java支持函數式編程的基礎。編程

本文目錄:數組

1 Java8函數式編程語法入門

Java8中函數式編程語法可以精簡代碼。
使用Consumer做爲示例,它是一個函數式接口,包含一個抽象方法accept,這個方法只有輸入而無輸出。
如今咱們要定義一個Consumer對象,傳統的方式是這樣定義的:

Consumer c = new Consumer() {
    @Override
    public void accept(Object o) {
        System.out.println(o);
    }
};

而在Java8中,針對函數式編程接口,能夠這樣定義:

Consumer c = (o) -> {
    System.out.println(o);
};

上面已說明,函數式編程接口都只有一個抽象方法,所以在採用這種寫法時,編譯器會將這段函數編譯後看成該抽象方法的實現。
若是接口有多個抽象方法,編譯器就不知道這段函數應該是實現哪一個方法的了。
所以,=後面的函數體咱們就能夠當作是accept函數的實現。

  • 輸入:->前面的部分,即被()包圍的部分。此處只有一個輸入參數,實際上輸入是能夠有多個的,如兩個參數時寫法:(a, b);固然也能夠沒有輸入,此時直接就能夠是()。
  • 函數體:->後面的部分,即被{}包圍的部分;能夠是一段代碼。
  • 輸出:函數式編程能夠沒有返回值,也能夠有返回值。若是有返回值時,須要代碼段的最後一句經過return的方式返回對應的值。

當函數體中只有一個語句時,能夠去掉{}進一步簡化:

Consumer c = (o) -> System.out.println(o);

然而這還不是最簡的,因爲此處只是進行打印,調用了System.out中的println靜態方法對輸入參數直接進行打印,所以能夠簡化成如下寫法:

Consumer c = System.out::println;

它表示的意思就是針對輸入的參數將其調用System.out中的靜態方法println進行打印。
到這一步就能夠感覺到函數式編程的強大能力。
經過最後一段代碼,咱們能夠簡單的理解函數式編程,Consumer接口直接就能夠當成一個函數了,這個函數接收一個輸入參數,而後針對這個輸入進行處理;固然其本質上仍舊是一個對象,但咱們已經省去了諸如老方式中的對象定義過程,直接使用一段代碼來給函數式接口對象賦值。
並且最爲關鍵的是,這個函數式對象由於本質上仍舊是一個對象,所以能夠作爲其它方法的參數或者返回值,能夠與原有的代碼實現無縫集成!

下面對Java中的幾個預先定義的函數式接口及其常用的類進行分析學習。

2 Java函數式接口

2.1 Consumer

Consumer是一個函數式編程接口; 顧名思義,Consumer的意思就是消費,即針對某個東西咱們來使用它,所以它包含有一個有輸入而無輸出的accept接口方法;
除accept方法,它還包含有andThen這個方法;
其定義以下:

default Consumer<T> andThen(Consumer<? super T> after) {
    Objects.requireNonNull(after);
    return (T t) -> { accept(t); after.accept(t); };
}

可見這個方法就是指定在調用當前Consumer後是否還要調用其它的Consumer;
使用示例:

public static void consumerTest() {
    Consumer f = System.out::println;
    Consumer f2 = n -> System.out.println(n + "-F2");

    //執行完F後再執行F2的Accept方法
    f.andThen(f2).accept("test");

    //連續執行F的Accept方法
    f.andThen(f).andThen(f).andThen(f).accept("test1");
}

2.2 Function

Function也是一個函數式編程接口;它表明的含義是「函數」,而函數常常是有輸入輸出的,所以它含有一個apply方法,包含一個輸入與一個輸出;
除apply方法外,它還有compose與andThen及indentity三個方法,其使用見下述示例;

/**
 * Function測試
 */
public static void functionTest() {
    Function<Integer, Integer> f = s -> s++;
    Function<Integer, Integer> g = s -> s * 2;

    /**
     * 下面表示在執行F時,先執行G,而且執行F時使用G的輸出看成輸入。
     * 至關於如下代碼:
     * Integer a = g.apply(1);
     * System.out.println(f.apply(a));
     */
    System.out.println(f.compose(g).apply(1));

    /**
     * 表示執行F的Apply後使用其返回的值看成輸入再執行G的Apply;
     * 至關於如下代碼
     * Integer a = f.apply(1);
     * System.out.println(g.apply(a));
     */
    System.out.println(f.andThen(g).apply(1));

    /**
     * identity方法會返回一個不進行任何處理的Function,即輸出與輸入值相等; 
     */
    System.out.println(Function.identity().apply("a"));
}

2.3 Predicate

Predicate爲函數式接口,predicate的中文意思是「判定」,即判斷的意思,判斷某個東西是否知足某種條件; 所以它包含test方法,根據輸入值來作邏輯判斷,其結果爲True或者False。
它的使用方法示例以下:

/**
 * Predicate測試
 */
private static void predicateTest() {
    Predicate<String> p = o -> o.equals("test");
    Predicate<String> g = o -> o.startsWith("t");

    /**
     * negate: 用於對原來的Predicate作取反處理;
     * 如當調用p.test("test")爲True時,調用p.negate().test("test")就會是False;
     */
    Assert.assertFalse(p.negate().test("test"));

    /**
     * and: 針對同一輸入值,多個Predicate均返回True時返回True,不然返回False;
     */
    Assert.assertTrue(p.and(g).test("test"));

    /**
     * or: 針對同一輸入值,多個Predicate只要有一個返回True則返回True,不然返回False
     */
    Assert.assertTrue(p.or(g).test("ta"));
}

3 函數式編程接口的使用

經過Stream以及Optional兩個類,能夠進一步利用函數式接口來簡化代碼。

3.1 Stream

Stream能夠對多個元素進行一系列的操做,也能夠支持對某些操做進行併發處理。

3.1.1 Stream對象的建立

Stream對象的建立途徑有如下幾種

a. 建立空的Stream對象

Stream stream = Stream.empty();

b. 經過集合類中的stream或者parallelStream方法建立;

List<String> list = Arrays.asList("a", "b", "c", "d");
Stream listStream = list.stream();                   //獲取串行的Stream對象
Stream parallelListStream = list.parallelStream();   //獲取並行的Stream對象

c. 經過Stream中的of方法建立:

Stream s = Stream.of("test");
Stream s1 = Stream.of("a", "b", "c", "d");

d. 經過Stream中的iterate方法建立:
iterate方法有兩個不一樣參數的方法:

public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f);  
public static<T> Stream<T> iterate(T seed, Predicate<? super T> hasNext, UnaryOperator<T> next)

其中第一個方法將會返回一個無限有序值的Stream對象:它的第一個元素是seed,第二個元素是f.apply(seed); 第N個元素是f.apply(n-1個元素的值);生成無限值的方法實際上與Stream的中間方法相似,在遇到停止方法前通常是不真正的執行的。所以無限值的這個方法通常與limit等方法一塊兒使用,來獲取前多少個元素。
固然獲取前多少個元素也可使用第二個方法。
第二個方法與第一個方法生成元素的方式相似,不一樣的是它返回的是一個有限值的Stream;停止條件是由hasNext來判定的。

第二種方法的使用示例以下:

/**
 * 本示例表示從1開始組裝一個序列,第一個是1,第二個是1+1即2,第三個是2+1即3..,直接10時停止;
 * 也可簡化成如下形式:
 *        Stream.iterate(1,
 *        n -> n <= 10,
 *        n -> n+1).forEach(System.out::println);
 * 寫成如下方式是爲簡化理解
 */
Stream.iterate(1,
        new Predicate<Integer>() {
            @Override
            public boolean test(Integer integer) {
                return integer <= 10;
            }
        },
    new UnaryOperator<Integer>() {
        @Override
        public Integer apply(Integer integer) {
            return integer+1;
        }
}).forEach(System.out::println);

e. 經過Stream中的generate方法建立
與iterate中建立無限元素的Stream相似,不過它的每一個元素與前一元素無關,且生成的是一個無序的隊列。也就是說每個元素均可以隨機生成。所以通常用來建立常量的Stream以及隨機的Stream等。
示例以下:

/**
 * 隨機生成10個Double元素的Stream並將其打印
 */
Stream.generate(new Supplier<Double>() {
    @Override
    public Double get() {
        return Math.random();
    }
}).limit(10).forEach(System.out::println);

//上述寫法能夠簡化成如下寫法:
Stream.generate(() -> Math.random()).limit(10).forEach(System.out::println);

f. 經過Stream中的concat方法鏈接兩個Stream對象生成新的Stream對象
這個比較好理解再也不贅述。

3.1.2 Stream對象的使用

Stream對象提供多個很是有用的方法,這些方法能夠分紅兩類:
中間操做:將原始的Stream轉換成另一個Stream;如filter返回的是過濾後的Stream。
終端操做:產生的是一個結果或者其它的複合操做;如count或者forEach操做。

其清單以下所示,方法的具體說明及使用示例見後文。
全部中間操做

方法 說明
sequential 返回一個相等的串行的Stream對象,若是原Stream對象已是串行就可能會返回原對象
parallel 返回一個相等的並行的Stream對象,若是原Stream對象已是並行的就會返回原對象
unordered 返回一個不關心順序的Stream對象,若是原對象已是這類型的對象就會返回原對象
onClose 返回一個相等的Steam對象,同時新的Stream對象在執行Close方法時會調用傳入的Runnable對象
close 關閉Stream對象
filter 元素過濾:對Stream對象按指定的Predicate進行過濾,返回的Stream對象中僅包含未被過濾的元素
map 元素一對一轉換:使用傳入的Function對象對Stream中的全部元素進行處理,返回的Stream對象中的元素爲原元素處理後的結果
mapToInt 元素一對一轉換:將原Stream中的使用傳入的IntFunction加工後返回一個IntStream對象
flatMap 元素一對多轉換:對原Stream中的全部元素進行操做,每一個元素會有一個或者多個結果,而後將返回的全部元素組合成一個統一的Stream並返回;
distinct 去重:返回一個去重後的Stream對象
sorted 排序:返回排序後的Stream對象
peek 使用傳入的Consumer對象對全部元素進行消費後,返回一個新的包含全部原來元素的Stream對象
limit 獲取有限個元素組成新的Stream對象返回
skip 拋棄前指定個元素後使用剩下的元素組成新的Stream返回
takeWhile 若是Stream是有序的(Ordered),那麼返回最長命中序列(符合傳入的Predicate的最長命中序列)組成的Stream;若是是無序的,那麼返回的是全部符合傳入的Predicate的元素序列組成的Stream。
dropWhile 與takeWhile相反,若是是有序的,返回除最長命中序列外的全部元素組成的Stream;若是是無序的,返回全部未命中的元素組成的Stream。

全部終端操做

方法 說明
iterator 返回Stream中全部對象的迭代器;
spliterator 返回對全部對象進行的spliterator對象
forEach 對全部元素進行迭代處理,無返回值
forEachOrdered 按Stream的Encounter所決定的序列進行迭代處理,無返回值
toArray 返回全部元素的數組
reduce 使用一個初始化的值,與Stream中的元素一一作傳入的二合運算後返回最終的值。每與一個元素作運算後的結果,再與下一個元素作運算。它不保證會按序列執行整個過程。
collect 根據傳入參數作相關匯聚計算
min 返回全部元素中最小值的Optional對象;若是Stream中無任何元素,那麼返回的Optional對象爲Empty
max 與Min相反
count 全部元素個數
anyMatch 只要其中有一個元素知足傳入的Predicate時返回True,不然返回False
allMatch 全部元素均知足傳入的Predicate時返回True,不然False
noneMatch 全部元素均不知足傳入的Predicate時返回True,不然False
findFirst 返回第一個元素的Optioanl對象;若是無元素返回的是空的Optional; 若是Stream是無序的,那麼任何元素均可能被返回。
findAny 返回任意一個元素的Optional對象,若是無元素返回的是空的Optioanl。
isParallel 判斷是否當前Stream對象是並行的

下面就幾個比較經常使用的方法舉例說明其用法:

3.1.2.1 filter

用於對Stream中的元素進行過濾,返回一個過濾後的Stream
其方法定義以下:

Stream<T> filter(Predicate<? super T> predicate);

使用示例:

Stream<String> s = Stream.of("test", "t1", "t2", "teeeee", "aaaa");
//查找全部包含t的元素並進行打印
s.filter(n -> n.contains("t")).forEach(System.out::println);
3.1.2.2 map

元素一對一轉換。
它接收一個Funcation參數,用其對Stream中的全部元素進行處理,返回的Stream對象中的元素爲Function對原元素處理後的結果
其方法定義以下:

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

示例,假設咱們要將一個String類型的Stream對象中的每一個元素添加相同的後綴.txt,如a變成a.txt,其寫法以下:

Stream<String> s = Stream.of("test", "t1", "t2", "teeeee", "aaaa");
s.map(n -> n.concat(".txt")).forEach(System.out::println);
3.1.2.3 flatMap

元素一對多轉換:對原Stream中的全部元素使用傳入的Function進行處理,每一個元素通過處理後生成一個多個元素的Stream對象,而後將返回的全部Stream對象中的全部元素組合成一個統一的Stream並返回;
方法定義以下:

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

示例,假設要對一個String類型的Stream進行處理,將每個元素的拆分紅單個字母,並打印:

Stream<String> s = Stream.of("test", "t1", "t2", "teeeee", "aaaa");
s.flatMap(n -> Stream.of(n.split(""))).forEach(System.out::println);
3.1.2.4 takeWhile

方法定義以下:

default Stream<T> takeWhile(Predicate<? super T> predicate)

若是Stream是有序的(Ordered),那麼返回最長命中序列(符合傳入的Predicate的最長命中序列)組成的Stream;若是是無序的,那麼返回的是全部符合傳入的Predicate的元素序列組成的Stream。
與Filter有點相似,不一樣的地方就在當Stream是有序時,返回的只是最長命中序列。
如如下示例,經過takeWhile查找」test」, 「t1」, 「t2」, 「teeeee」, 「aaaa」, 「taaa」這幾個元素中包含t的最長命中序列:

Stream<String> s = Stream.of("test", "t1", "t2", "teeeee", "aaaa", "taaa");
//如下結果將打印: "test", "t1", "t2", "teeeee",最後的那個taaa不會進行打印 
s.takeWhile(n -> n.contains("t")).forEach(System.out::println);
3.1.2.5 dropWhile

與takeWhile相反,若是是有序的,返回除最長命中序列外的全部元素組成的Stream;若是是無序的,返回全部未命中的元素組成的Stream;其定義以下:

default Stream<T> dropWhile(Predicate<? super T> predicate)

如如下示例,經過dropWhile刪除」test」, 「t1」, 「t2」, 「teeeee」, 「aaaa」, 「taaa」這幾個元素中包含t的最長命中序列:

Stream<String> s = Stream.of("test", "t1", "t2", "teeeee", "aaaa", "taaa");
//如下結果將打印:"aaaa", "taaa" 
s.dropWhile(n -> n.contains("t")).forEach(System.out::println);
3.1.2.6 reduce與collect

關於reduce與collect因爲功能較爲複雜,在後續將進行單獨分析與學習,此處暫不涉及。

3.2 Optional

用於簡化Java中對空值的判斷處理,以防止出現各類空指針異常。
Optional其實是對一個變量進行封裝,它包含有一個屬性value,實際上就是這個變量的值。

3.2.1 Optional對象建立

它的構造函數都是private類型的,所以要初始化一個Optional的對象沒法經過其構造函數進行建立。它提供了一系列的靜態方法用於構建Optional對象:

3.2.1.1 empty

用於建立一個空的Optional對象;其value屬性爲Null。
如:

Optional o = Optional.empty();
3.2.1.2 of

根據傳入的值構建一個Optional對象;
傳入的值必須是非空值,不然若是傳入的值爲空值,則會拋出空指針異常。
使用:

o = Optional.of("test");
3.2.1.3 ofNullable

根據傳入值構建一個Optional對象
傳入的值能夠是空值,若是傳入的值是空值,則與empty返回的結果是同樣的。

3.2.2 方法

Optional包含如下方法:

方法名 說明
get 獲取Value的值,若是Value值是空值,則會拋出NoSuchElementException異常;所以返回的Value值無需再作空值判斷,只要沒有拋出異常,都會是非空值。
isPresent Value是否爲空值的判斷;
ifPresent 當Value不爲空時,執行傳入的Consumer;
ifPresentOrElse Value不爲空時,執行傳入的Consumer;不然執行傳入的Runnable對象;
filter 當Value爲空或者傳入的Predicate對象調用test(value)返回False時,返回Empty對象;不然返回當前的Optional對象
map 一對一轉換:當Value爲空時返回Empty對象,不然返回傳入的Function執行apply(value)後的結果組裝的Optional對象;
flatMap 一對多轉換:當Value爲空時返回Empty對象,不然傳入的Function執行apply(value)後返回的結果(其返回結果直接是Optional對象)
or 若是Value不爲空,則返回當前的Optional對象;不然,返回傳入的Supplier生成的Optional對象;
stream 若是Value爲空,返回Stream對象的Empty值;不然返回Stream.of(value)的Stream對象;
orElse Value不爲空則返回Value,不然返回傳入的值;
orElseGet Value不爲空則返回Value,不然返回傳入的Supplier生成的值;
orElseThrow Value不爲空則返回Value,不然拋出Supplier中生成的異常對象;

3.2.3 使用場景

經常使用的使用場景以下:

3.2.3.1 判斷結果不爲空後使用

如某個函數可能會返回空值,以往的作法:

String s = test();
if (null != s) {
    System.out.println(s);
}

如今的寫法就能夠是:

Optional<String> s = Optional.ofNullable(test());
s.ifPresent(System.out::println);

乍一看代碼複雜度上差很少甚至是略有提高;那爲何要這麼作呢?
通常狀況下,咱們在使用某一個函數返回值時,要作的第一步就是去分析這個函數是否會返回空值;若是沒有進行分析或者分析的結果出現誤差,致使函數會拋出空值而沒有作檢測,那麼就會相應的拋出空指針異常!
而有了Optional後,在咱們不肯定時就能夠不用去作這個檢測了,全部的檢測Optional對象都幫忙咱們完成,咱們要作的就是按上述方式去處理。

3.2.3.2 變量爲空時提供默認值

如要判斷某個變量爲空時使用提供的值,而後再針對這個變量作某種運算;
以往作法:

if (null == s) {
    s = "test";
}
System.out.println(s);

如今的作法:

Optional<String> o = Optional.ofNullable(s);
System.out.println(o.orElse("test"));
3.2.3.3 變量爲空時拋出異常,不然使用

以往寫法:

if (null == s) {
    throw new Exception("test");
}
System.out.println(s);

如今寫法:

Optional<String> o = Optional.ofNullable(s);
System.out.println(o.orElseThrow(()->new Exception("test")));
相關文章
相關標籤/搜索