引言
在JDK1.8的新特性中,引入了一個叫Lambda表達式的東西,或許有些小夥伴到現在都沒有寫過。它的核心理念就是“可以像對象一樣傳遞匿名函數”。在本篇博文中,我會詳細介紹Lambda的概念,同時介紹幾個JDK1.8中新增加的一些類和關鍵性的註解,在後面會給出一些簡單的Lambda的例子供大家交流。筆者目前整理的一些blog針對面試都是超高頻出現的。大家可以點擊鏈接:http://blog.csdn.net/u012403290
技術點
1、匿名內部類
在java中,繼承一個父類或者實現一個接口的時候,爲了書寫簡單,可以使用匿名內部類,比如說下面這段代碼:
//一個接口,一個待實現的方法
package com.brickworkers;
/**
*
* @author Brickworker
* Date:2017年4月14日下午2:24:23
* 關於類LamdaInterface.java的描述:匿名內部類展示
* Copyright (c) 2017, brcikworker All Rights Reserved.
*/
@FunctionalInterface
public interface LamdaInterface {
void print();
}
//在類中採用靜態內部類直接實現接口方法
package com.brickworkers;
public class LamdaImpl{
public static void main(String[] args) {
LamdaInterface lamdaInterface = new LamdaInterface() {//匿名內部類直接實現接口
@Override
public void print() {
System.err.println("helloworld");
}
};
lamdaInterface.print();
}
}
//輸出:helloworld
上面這個例子中就是我們用匿名內部類實現了接口中的方法,其實還有更常見的,在我們新寫一個線程的時候:
Thread t1 = new Thread(new Runnable() {//匿名內部類直接實現Runnable接口
@Override
public void run() {
System.out.println("t1 run");
}
});
t1.start();
通過匿名內部類實現接口相信大家有所瞭解了,那麼我再繼承中如何實現呢?比如說我要重寫父類中的某個方法:
package com.brickworkers;
import java.util.ArrayList;
import java.util.List;
public class LamdaImpl{
public static void main(String[] args) {
List<String> str = new ArrayList<String>(){
@Override
public String toString() {
return "匿名內部類直接重寫List的toString方法";
}
};
System.out.println(str.toString());
}
}
//輸出:匿名內部類直接重寫List的toString方法
這樣一來,就不需要重寫寫一個類專門處理一些方法的重寫了,匿名內部類能夠很好的簡化代碼,也可以增強代碼的可讀性。
2、函數接口@FunctionalInterface
簡單來說就是一個接口中只有唯一的一個函數。比如說我們常用的Runnable接口,就只存在一個待實現的函數,以下是Runnable的源碼:
*/
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
同時,如果這個接口是函數接口,那麼需要用註解標註@FunctionalInterface,能否使用Lambda表達式要看這個接口是否存在這個註解,這個註解中,JDK對它的解釋是:
* <p>Note that instances of functional interfaces can be created with
* lambda expressions, method references, or constructor references.
所以Runable這個接口是可以使用Lambda表達式的。
但是這裏要說明一點,因爲所有的類都繼承Object類,所以如果是重寫了toString等屬於Object的方法並不屬於接口中的一個函數。
3、Lambda表達式
“Lambda 表達式”(lambda expression)是一個匿名函數,Lambda表達式基於數學中的λ演算得名,直接對應於其中的lambda抽象(lambda abstraction),是一個匿名函數,即沒有函數名的函數。Lambda表達式可以表示閉包 -百度詞條
之所以稱之爲匿名函數,因爲
比如說它在表達x+y的時候是這麼玩的:
(x,y) -> x+y;
再比如void函數:
() - > {system.out.println()}
具體不再詳細介紹,看下面例子就可以了。
3、介紹幾個JDK1.8中新的類和函數接口
①Function函數接口:主要是接收一個類型的參數,返回另外一個類型的結果,比如說入參是String,返回是Intger,那麼就是一個類型轉換的功能。
②Consumer函數接口:主要是接收一個類型參數,void方法,沒有返回。
③Predicate函數接口:主要是接收一個類型參數,boolean方法,可以進行條件判斷。
簡單的Lambda例子
①我們可以用Lambda表達來寫上面描述到的匿名內部類,比如說你現在要寫一個線程,再也不用需要用上面用匿名內部類來表示了,你可以直接:
package com.brickworkers;
public class LamdaImpl{
public static void main(String[] args) {
Thread t1 = new Thread(() -> {System.out.println("t1 執行");});
t1.start();
}
}
//輸出:t1 執行
你不需要去指定Runable接口,JVM會自己會根據上下文進行識別,是不是比原來方便了很多呢?然後我們對“() -> {System.out.println(“t1 執行”);}”這部分進行解析,()表示入參,這個表示沒有入參; ->表示run方法,爲什麼表示run方法?因爲函數接口中只會存在一個方法。鼠標放在這個上面會展示詳細信息:
{System.out.println(“t1 執行”);}其實就是放在run方法中的需要執行的代碼塊。
②在JDK1.5中引入了增強for循環,稱爲for-each,在JDK1.8中引入更簡便的迭代方式,就是用Lanbda來替代for-each遍歷,請看下面的例子:
package com.brickworkers;
import java.util.ArrayList;
import java.util.List;
public class LamdaImpl{
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
for (int i = 0; i < 10; i++) {
list.add(i+"");
}
System.out.print("for-each遍歷:");
for (String string : list) {
System.out.print(string+" ");
}
System.out.print("Lanbda遍歷:");
list.forEach(o -> {System.out.print(o+" ");});
}
}
//輸出結果: for-each遍歷:0 1 2 3 4 5 6 7 8 9 Lanbda遍歷:0 1 2 3 4 5 6 7 8 9
其實是在Iterable接口中多了這個forEach的方法,它的底層其實還是for - each的迭代方式,以下是forEach的源碼:
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
之所以能使用Lambda表達式是因爲入參是Consumer這個函數接口。
稍高端的Lambda表達式例子
③在JDK1.8中還增加了Stream API,充分利用現代多核CPU,可以寫出更加簡潔的代碼,這裏我們不考慮多線程,我們就簡單說說Lambda表達式在流中是怎麼操作的。
比如說我們在在一個String的List中找到包含名叫“brickworker”的人,代碼如下:
package com.brickworkers;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class LamdaImpl{
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("Amy");
list.add("Gary");
list.add("tom");
list.add("tom");
list.add("brickworker");
list.add("brickworker2");
list.add("brickworker3");
List<String> brickworkers = list.stream().filter(s -> s.indexOf("brickworker") >-1).collect(Collectors.toList());
brickworkers.forEach(s -> System.out.println(s));
}
}
//輸出結果
//brickworker
//brickworker2
//brickworker3
仔細觀察上面代碼,其實分爲了幾步:
a、list.stream()把list中的數據轉化成一個流
b、filter把流中的數據中保留包含brickwork的數據,其他的數據刪除
c、collect把流重寫轉換成一個List
在來一個更難的例子,比如說,在開發中發現有一個String的List,它主要存儲了用戶的年齡,我們希望能夠拿出年齡在20歲以下的用戶並單獨存儲起來,那麼我們就可以通過流和Lambda的形式快速實現:
package com.brickworkers;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class LamdaImpl{
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("13");
list.add("14");
list.add("15");
list.add("20");
list.add("33");
list.add("22");
list.add("45");
List<Integer> ages = list.stream().map(s -> new Integer(s)).filter(s -> s < 20).collect(Collectors.toList());
ages.forEach(s -> System.out.println(s));
}
}
//輸出結果:
//13
//14
//15
在這個過程中,在比較的過程中需要用int型進行比較,但是因爲爲是一個String的鏈表,我們需要先通過map的方法將他進行轉型。在map的這個方法中,入參其實就是我們上面提到的Function函數接口,它支持入參一個類型,返回另外一個類型。以下是map的源碼:
/**
* Returns a stream consisting of the results of applying the given
* function to the elements of this stream.
*
* <p>This is an <a href="package-summary.html#StreamOps">intermediate
* operation</a>.
*
* @param <R> The element type of the new stream
* @param mapper a <a href="package-summary.html#NonInterference">non-interfering</a>,
* <a href="package-summary.html#Statelessness">stateless</a>
* function to apply to each element
* @return the new stream
*/
<R> Stream<R> map(Function<? super T, ? extends R> mapper);//Function接口進行轉型
當然了,在Collectors的靜態方法中不僅僅只有toList,還可以toMap和toSet等等,具體使用要看具體的場景。還有很多簡潔方便的使用方式,希望大家自己去探究。
尾記
關於Lambda就介紹這麼多,個人意見:在日常的開發中,如果你沒有見到過項目中有這樣的東西,就不要用Lambda來寫了,因爲大多的程序員沒有瞭解到這些東西,閱讀代碼會造成很大的困擾。同時,不常用肯定是有原因的,要麼不方便,要麼性能不好。但是面試的時候,可能面試官會考察你JDK版本的新特性哦。如果大家有什麼問題可以在下方留言交流,共同學習進步。