1. 程式人生 > >Java8學習筆記(1) -- 從函式式介面說起

Java8學習筆記(1) -- 從函式式介面說起

函式式介面

理解Functional Interface(函式式介面,以下簡稱FI)是學習Java8 Lambda表示式的關鍵所在,所以放在最開始討論。FI的定義其實很簡單:任何介面,如果只包含唯一一個抽象方法,那麼它就是一個FI。為了讓編譯器幫助我們確保一個介面滿足FI的要求(也就是說有且僅有一個抽象方法),Java8提供了@FunctionalInterface註解。舉個簡單的例子,Runnable介面就是一個FI,下面是它的原始碼:

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

Lambda語法糖

為了能夠方便、快捷、幽雅的創建出FI的例項,Java8提供了Lambda表示式這顆語法糖。下面我用一個例子來介紹Lambda語法。假設我們想對一個List<String>按字串長度進行排序,那麼在Java8之前,可以藉助匿名內部類來實現:

List<String> words = Arrays.asList("apple", "banana", "pear");
words.sort(new Comparator<String>() {

    @Override
    public int compare(String w1, String w2) {
        return Integer.compare(w1.length(), w2.length());
    }

});

上面的匿名內部類簡直可以用醜陋來形容,唯一的一行邏輯被五行垃圾程式碼淹沒。根據前面的定義(並檢視Java原始碼)可知,Comparator是個FI,所以,可以用Lambda表示式來實現:

words.sort((String w1, String w2) -> {
    return Integer.compare(w1.length(), w2.length());
});
程式碼變短了好多!仔細觀察就會發現,Lambda表示式,很像一個匿名的方法,只是圓括號內的引數列表和花括號內的程式碼被->分隔開了。垃圾程式碼寫的越少,我們就有越多的時間去寫真正的邏輯程式碼,不是嗎?是的!圓括號裡的引數型別是可以省略的:
words.sort((w1, w2) -> {
    return Integer.compare(w1.length(), w2.length());
});
如果Lambda表示式的程式碼塊只是return後面跟一個表示式,那麼還可以進一步簡化:
words.sort(
    (w1, w2) -> Integer.compare(w1.length(), w2.length())
);
注意,表示式後面是沒有分號的!如果只有一個引數,那麼包圍引數的圓括號可以省略:
words.forEach(word -> {
    System.out.println(word);
});
如果表示式不需要引數呢?好吧,那也必須有圓括號,例如:
Executors.newSingleThreadExecutor().execute(
    () -> {/* do something. */} // Runnable
);

方法引用

有時候Lambda表示式的程式碼就只是一個簡單的方法呼叫而已,遇到這種情況,Lambda表示式還可以進一步簡化為方法引用(Method References)。一共有四種形式的方法引用,第一種引用靜態方法,例如:

List<Integer> ints = Arrays.asList(1, 2, 3);
ints.sort(Integer::compare);
第二種引用某個特定物件的例項方法,例如前面那個遍歷並列印每一個word的例子可以寫成這樣:
words.forEach(System.out::println);
第三種引用某個類的例項方法,例如:
words.stream().map(word -> word.length()); // lambda
words.stream().map(String::length); // method reference

第四種引用類的建構函式,例如:

// lambda
words.stream().map(word -> {
    return new StringBuilder(word);
});
// constructor reference
words.stream().map(StringBuilder::new);

什麼時候用Lambda表示式

既然Lambda表示式這麼好用,那麼,可以在哪些地方使用呢?如果你真正明白了什麼是FI(很容易),應該立刻就能給出答案:任何可以接受一個FI例項的地方,都可以用Lambda表示式。比如,雖然上面給出的例子都是把Lambda表示式當作方法引數傳遞,但實際上你也可以定義變數:

Runnable task = () -> {
    // do something
};

Comparator<String> cmp = (s1, s2) -> {
    return Integer.compare(s1.length(), s2.length());
};

預定義函式式介面

Java8除了給Runnable,Comparator等介面打上了@FunctionalInterface註解之外,還預定義了一大批新的FI。這些介面都在java.util.function包裡,下面簡單介紹其中的幾個。

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}
Predicate用來判斷一個物件是否滿足某種條件,比如,單詞是否由六個以上字母組成:
words.stream()
    .filter(word -> word.length() > 6)
    .count();

Function表示接收一個引數,併產生一個結果的函式:

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}
下面的例子將集合裡的每一個整數都乘以2:
ints.stream().map(x -> x * 2);

Consumer表示對單個引數進行的操作,前面例子中的forEach()方法接收的引數就是這種操作:

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}

對原有API的增強

為了充分發揮Lambda的威力,Java8對很多老的類庫進行了增強,給它們配備了Lambda武器。比如前面例子中用到的forEach()方法,實際上是新增到Iterable介面中的。而多次出現的stream()方法,則是新增在了Collection接口裡。用過Ruby,Scala,Groovy等語言的Java程式設計師,可能已經對在這些語言裡很好實現的外部迭代器模式垂涎很久了。雖然Google的Guava可以在一定程度上彌補Java的這種缺陷,但是Java8的Lambda才真正讓Java朝著函數語言程式設計邁進了一大步。

介面的預設方法

細心的讀者可能會發現一個問題,給Iterable和Collection等介面增加方法,豈不是會破壞介面的向後相容性?是的,為了保證API的向後相容性,Java8對介面的語法進行了較大的調整,增加了預設方法(Default Methods)。下面是forEach()方法的實現程式碼:

public interface Iterable<T> {
    ...
    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }
    ...
}

介面的靜態方法

除了抽象方法和預設方法,從Java8開始,介面也可以有靜態(static)方法了。有了這個語法,我們就可以把和介面相關的幫助方法(Helper Methods)直接定義在接口裡了。比如Function介面就定義了一個工廠方法indentity():

public interface Function<T, R> {
    ...
    /**
     * Returns a function that always returns its input argument.
     *
     * @param <T> the type of the input and output objects to the function
     * @return a function that always returns its input argument
     */
    static <T> Function<T, T> identity() {
        return t -> t;
    }
...
}

變數捕獲

內部類一樣,Lambda也可以訪問外部(詞法作用域)變數,規則基本一樣。Java8之前,內部類只能訪問final型別的變數,Java8放寬了這種限制,只要變數實際上不可變(effectively final)就可以。換句話說,如果你給變數加上final關鍵字編譯器也不報錯,那麼去掉final關鍵字後,它就是effectively final的。看下面的例子:

int a = 100;
Runnable x = new Runnable() {

    @Override
    public void run() {
        System.out.println(a);
    }
    
};
在Java8之前,a必須是final的才能被x看到。下面用Lambda表示式重寫上面的例子:
int a = 100;
Runnable x = () -> {
    System.out.println(a);
};

結論

可以看到,為了支援Lambda表示式,Java8對Java語言做了很大的調整。但Lambda表示式並非只是Java語法糖,而是由編譯器和JVM共同配合來實現的,這一點我會在下一篇文章裡詳細介紹。

相關推薦

Java8學習筆記1 -- 式式介面

函式式介面 理解Functional Interface(函式式介面,以下簡稱FI)是學習Java8 Lambda表示式的關鍵所在,所以放在最開始討論。FI的定義其實很簡單:任何介面,如果只包含唯一一個抽象方法,那麼它就是一個FI。為了讓編譯器幫助我們確保一個介面滿足FI

深度學習系列1——線性迴歸說

為什麼先說線性迴歸 本文適合入門級同學,老司機繞行。 吳恩達老師的機器學習課程,介紹的第一個模型就是線性迴歸模型。機器學習(尤其是監督學習),主要圍繞分類和迴歸兩類問題展開,而線性迴歸模型作為最簡單的迴歸模型,與大多數監督學習演算法具有相同的建模思路,包括建

TX2入門到放棄學習筆記1-基礎

一.資源簡介 TX2作為TX1的改進版,資源配置相當強勁。 1. 模組配置  256 core NVIDIA Pascal GPU.  ARMv8 (64-bit) Multi-Processor CPU Complex.  Advan

《深入分散式快取:原理到實踐》學習筆記1

第一章:快取為王 快取為王,不同的語境中所代表的快取意義不同。 快取的一個主要目的在於提高使用者體驗,是一種非功能性約束。 大型網站架構 頁面快取,不用多次渲染 頁面自身對元素進行快取; 服務端黃金靜態頁面或動態頁

JAVA學習筆記1——a++與++a的區別

col int 演示 opera 解析 代碼 數據 ++i div 需求:此博客用於解釋i++與++i的區別。 過程: 1、名稱解釋 ++:自增,即在原有數據基礎上+1,再賦給原有數據。 2、程序演示 (1)代碼: 1 class OperateDemo 2 { 3

vray學習筆記1

com .cn 過程 分組 是把 皮膚 mon image 基本 vray是個什麽東西? 它是個渲染器。 渲染器是個什麽東西? 渲染器就是3d軟件裏面把模型畫成一張圖片的東西,渲染的過程就是把3D物體變成2D畫面的過程。 模型是個什麽東西? 模型就是模型,它由兩部分組成,第

《深入理解C指針》學習筆記1--- 指針之外

結構 def form 學習 編程 stdlib.h struct 一個 char   C語言從誕生之初就非常善於和硬件打交道,經過這麽多年的發展之後,其靈活性和超強的特征是受到幾乎所有程序員的肯定。C語言的這種靈活性很大一部分程度來源與C指針,指針為C語言動態操控內存提供

CS231n 學習筆記1 Image CLassification

eight function 分享 便是 數據驅動 rain 分類問題 很難 特征 圖像分類是計算機視覺中的一項核心任務,那麽什麽是圖像分類? 例如,給你一個標簽集,其中包括(貓、狗、鳥、卡車、飛機...等) 然後給你一張圖片,那麽這張圖片屬於哪個類別呢?這就是一個分類

bootstrap 學習筆記1---介紹bootstrap和柵格系統

優先 cal 圖片 應用 尺寸 文件中 lin png ice   學習前端許久,對於布置框架和響應瀏覽器用html 和javascript 寫的有點繁瑣,無意間看到這個框架,覺得挺好用的就開始學習了,但是這個框架上面有很多知識,不是所有的都要學的,故將學習筆記和覺得重點的

《挑戰程序設計競賽》學習筆記 1

設計 allow 而且 硬幣 ack ket 程序設計 all 不能 2.2 貪心法 貪心法是遵循某種規則,不斷貪心選取當前最優策略的算法設計方法。 貪心法的求解思想是通過叠代地選取當前問題的局部最優解法來達成總體最優解,在叠代的過程中不斷地產生局部最優解和下一個與之前

AngularJs學習筆記1——ng-app

oot you ctrl span fun 代碼 問題 筆記 doctype 眾所周知: ng-app 指令用於告訴 AngularJS 應用當前這個元素是根元素。 所有 AngularJS 應用都必須要要一個根元素。 HTML 文檔中只允許有一個 ng-app 指令,如果

springmvc學習筆記 -- 零搭建,基礎入門

out hand char webapp core localhost list ges del 1、新建maven項目 參考mybatis學習筆記(五) -- maven+spring+mybatis從零開始搭建整合詳細過程(上)第一部分,修改配置 2、修

Nordic nRF52832 學習筆記1 介紹,入門,與準備工作

例程 盜版 path pdf 規範 準備 但是 依然 可能   近來,物聯網已成為大勢所趨,VR與AR正方興未艾,各種手環、遙控、智能家居也在粉墨登場。技術前沿的領航者們已經快馬加鞭,各種意誌與暗示也在上傳下達。物聯網,無線通訊,移動互聯,將成為新的目標與寵兒。最近開的電賽

javascript 高級程序設計學習筆記1

元素 新的 logs html light begin 知識 gin nbsp 知識補充: var box = document.querySelector(‘#box‘); //"beforebegin" ,在當前元素之前插入一個緊鄰的同輩元素; box.ins

Postgresql 學習筆記1

sql postgre 一、環境#配置遠程連接 su postgres vim /var/lib/pgsql/9.4/data/postgresql.conf 編輯配置文件 listen_address=’localhost’ 前面的註釋#去掉,並把’localhost’該為’*’;

ES6學習筆記—— async

ons fst cte code span pre getname 普通 聲明 await 是 async wait 的簡寫, 是 generator 函數的語法糖。 async 函數的特點: async 聲明一個方法是異步的,await 則等待這個異步方法執行的完

Hibernate學習筆記1---hibernate快速上手與準備工作

成了 -- 開源 工作 快速 tar ref orm 磁盤 持久層介紹 持久化:將內存中的數據保存在磁盤等存儲設備中。 持久化對象:指已經存儲在數據庫護著磁盤的業務對象 經典的軟件應用體系結構(三層結構) 在三層結構中,由於業務邏輯除了負責業務邏輯以外,還要負責相關的數據

jQuery源碼學習筆記1

ase tolower nodetype apt jquer 元素 bre 技術分享 停止 在慕課網上學習jQuery源碼,做一些筆記小研究。 第1章 節點遍歷 第2章 文檔處理 第3章 元素操作 第4章 樣式操作 第5章 事件體系 第6章 數據交互 第7章

Struts2學習筆記1---相關配置

XML def rec 模塊 定向 -1 開發 oba 合並 Struts 2是Struts的下一代產品,是在 struts 1和WebWork的技術基礎上進行了合並的全新的Struts 2框架。 1創建action對象(三種) 1 創建普通的類,不繼承任何類,也不

JAVA8學習筆記----三個預定義接口

筆記 mps pub cti set nal () ack temp 三個函數接口概述JDK預定義了很多函數接口以避免用戶重復定義。最典型的是Function:@FunctionalInterface public interface Function<T, R>