【JAVA8新特性】——函數式編程、Lambda表達式、流式API

一、前言


記錄這篇博客的目的主要是因爲不太熟悉Lambda表達式,也就順便瞅一瞅JAVA8有哪些好玩好用的東西

基本上可以有個大致的概念

  1. JAVA8的大部分新特性都是針對函數式編程和流式編程
  2. JAVA8的大部分新特性都是爲了編寫可讀性更高的代碼



二、Lambda表達式


lambda表達式的目的是能夠將函數作爲表達式和參數來進行傳遞

重要特徵

  1. 可選類型申明:不需要聲明參數類型,編譯器可以統一識別參數值
  2. 可選的參數圓括號:一個參數無需定義圓括號,但多個參數需要定義圓括號
  3. 可選的大括號:如果主體包含了一個語句,就不需要使用大括號
  4. 可選的返回關鍵字:如果主體只有一個表達式返回值,則編譯器會自動返回值,大括號需要指明表達式返回了一個數值

注意事項

lambda表達式不綁定thisthis是外層作用域的this


JS的箭頭函數

箭頭函數作爲函數體

const logFunc = (name, age) => {
    console.log(name + "" + age);
};

logFunc("教主", 20);

箭頭函數作爲參數

const vm = new Vue({
	data: function() {
		return { students: null };
	},
	mounted: function() {
		axios.get('localhost:1000/hello/students').then((response) => {
			this.studentList = response.data;
		});
	}
});

JAVA的Lambda表達式

在JAVA中,lambda表達式實現的本質是匿名內部類,使用到的局部變量必須是final限定的。因此lambda表達式對引用的變量只讀不寫。但同時JAVA8中編譯時又默認會給使用到的局部變量加上final限定,並不需要顯式指出

lambda表達式簡化匿名內部類

    public static void main(String[] args) {
        // 匿名類內部類對象作爲參數
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("啓動線程1");
            }
        });
        t.start();

        // Lambda表達式作爲參數
        new Thread(() -> {
            System.out.println("啓動線程2");
        }).start();
    }

lambda表達式簡化遍歷

    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);

        // for-each語句
        for (Integer a : list) {
            System.out.println(a);
        }

        // lambda表達式的for-each
        list.forEach((a) -> {
            System.out.println(a);
        });
    }



三、函數式接口

函數式接口在JAVA中指有且僅有一個抽象方法的接口。將函數式接口的匿名內部類對象作爲參數傳遞時,由於只有一個抽象方法就可以簡化爲lambda表達式

只有確保接口中有且僅有一個抽象方法, JAVA中的Lambda才能順利地進行推導

@FunctionalInterface註解

@Override註解一樣,都只起標識作用。編譯器將會強制檢查使用該註解的接口是否確實有且僅有一個抽象方法,否則將會報錯


JAVA8之前已經有的函數式接口

  • 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

JAVA8中新增的四大函數式接口

函數式接口 方法原型
消費型接口Consumer<T> void accept(T var1)
供給型接口Supplier<T> T get()
功能型接口Function<T, R> R apply(T var1)
斷言型接口Predicate<T> boolean test(T var1)

自定義函數式接口

目前有參無返回、無參有返回、有參有返回、無參無返回的常見類型都可以直接四大函數式接口和java.lang.Runnable接口實現。

lambda表達式傳參就是函數式接口的匿名內部類對象傳參

@FunctionalInterface
public interface ExtraFunction<R, P1, P2> {
    R apply(P1 var1, P2 var2);
}
    public static void func(Integer var1, Integer var2, ExtraFunction<Integer, Integer, Integer> action) {
        Integer result = action.apply(var1, var2);
    }



四、方法引用

lambda表達式組合四種函數式接口已經可以滿足絕大部分的函數傳參的需求,但是我們也注意到

  1. lambda表達式更適合於傳遞臨時的函數
  2. 如果某些函數已經存在,那麼用Lambda表達式還是會顯得代碼冗餘

爲了傳遞已有的函數,方法引用應運而生


方法引用的分類

方法引用 語法 對應的Lambda表達式
靜態方法引用 [類型名]::[靜態方法名] ([參數]) -> { [類調用靜態方法]([參數]); }
構造方法引用 [類型名]::new ([參數]) -> { [調用該類型構造方法]([參數]); }
對象的實例方法引用 [對象]::[實例方法] ([參數]) -> { [實例].[調用實例方法]([參數]); }
類的實例方法引用 [類名]::[實例方法] ([參數]) -> { [參數].[調用實例方法](); }

示例

    public static void main(String[] args) {
        // 數組的構造方法引用,本質是Function<T, R>型接口的匿名內部類對象
        Function<Integer, Integer[]> constructor = Integer[]::new;
        constructor.apply(10);


        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);

        
        // 對象的實例方法引用傳參
        list.forEach(System.out::println);
        // 等價於
        list.forEach((a) -> {
            System.out.println(a);
        });

        
        // 類的實例方法引用傳參(不太恰當的例子)
        list.forEach(Object::toString);
        // 等價於
        list.forEach((a) -> {
            a.toString();
        });
    }



五、流式API


參考自《Java8中的Streams API詳解

大致認識

Stream不是集合元素,它不是數據結構並不保存數據,它是有關算法和計算的。它更像一個高級版本的Iterator

原始版本的Iterator, 用戶只能顯式地一個一個遍歷元素並對其執行某些操作
高級版本的Stream, 用戶只要給出需要對其包含的元素執行的操作

流式API的一個好處就是好寫又好看。比如一端沒什麼意義的鏈式調用

    public static void main(String[] args) {
        Random random = new Random();
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            list.add(random.nextInt(100) + "");
        }

        list.stream()
            // 中間操作————將String轉換爲Integer
            .map(Integer::parseInt)
            // 中間操作————過濾相同值
            .distinct()
            // 中間操作————跳過10個升序排序
            .skip(10)
            .sorted()
            // 中間操作————跳過10個降序排序
            .skip(25)
            .sorted((a, b) -> -(a - b))
            // 中間操作————只要大於60的部分
            .filter(a -> a >= 60)
            // 終端操作————輸出
            .forEach(System.out::println);
    }

Stream的使用步驟

  1. 創建Stream——從一個數據源(如集合、數組)中獲取流
  2. 中間操作——一個操作的中間鏈,對數據源的數據進行操作
  3. 終止操作——一個終止操作,執行中間操作鏈,併產生結果
  4. 關閉流

常用的創建流方式

最常用的是對集合和數組,調用stream()方法獲取一個流


常用的中間操作

篩選與切片

  • filter(predicate)從流中排除某些操作
  • limit(n)截斷流使其元素不超過給定對象n
  • skip(n)返回跳過前n個元素的流。若流中元素不足n個,則返回一個空流,與limit(n)互補
  • distinct()通過流所生成元素的hashCode()equals()去除重複元素

映射

  • map(mapper)將元素轉換成其他形式
  • flatMap(mapper)將流中的每個元素都換成另一個流,然後把所有流連接成一個流

排序

  • sorted()自然升序
  • sorted(comparator)定製排序

常用的終端操作

遍歷

  • forEach(action) 遍歷流中所有元素

輸出

  • toArray() 將流轉換爲數組
  • collect(collector)輸出其它集合。比如collect(Collectors.toList())輸出成一個List
  • reduce(accumulator) 合併流中的元素。比如累加運算

判斷

  • anyMatch(predicate) 流中是否至少包含一個符合特定條件的元素
  • allMatch(predicate) 流中是否至少每個元素都符合特定條件
  • noneMatch(predicate)流中是否所有元素都不符合特定條件

查詢

  • min()
  • max()
  • count()
  • findFirst()



六、總結


JAVA8還有很多新特性也是挺實用的。比如

  1. 接口的default方法和static方法更易於使用模板方法模式了
  2. Optional類的包裝簡化了空指針的判斷
  3. 更好的類型推斷(如若不然,現在的泛型接口豈不是要把人寫死嘛)

但是最主要的還是函數式編程和流式API,基本上革命性的改變了代碼編寫的風格,可讀性更高了,編寫和修改更加容易了…


不過對教主來說其實能夠使用的也就那麼幾個,畢竟接觸到的函數式接口基本都是最常用到的,而常用的函數式接口基本也就幾個,傳參基本也就消費型邏輯的函數、供給型邏輯的函數、斷言型邏輯的函數、功能型邏輯的函數…經常使用總是可以記住的嘛

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章