JDK8 于2014年發(fā)布至今,已有6個年頭。其中幾個重要的特性,lambda表達式,函數(shù)式接口,方法引用,默認方法等,至今未完全普及。在回到家鄉(xiāng)邯鄲這個三線小城市后,竟然有同事問我,有沒有簡單的統(tǒng)計List數(shù)據(jù)的方法,頗讓我驚訝。雖然我認為一個對公司有貢獻的程序員,首先應(yīng)該熟悉業(yè)務(wù)。但是我也擔心,技術(shù)上落后太多,也會拖累項目的性能。因此,本著服務(wù)家鄉(xiāng)的想法,梳理一下Java程序員基本的知識點。與互聯(lián)網(wǎng)項目不同,我更加重視基礎(chǔ)知識。對于微服務(wù)框架,我認為絕大多數(shù)應(yīng)用,是用不到的。如果為了使用而使用,只會增加團隊不必要的負擔。閑話不多說,我們開始介紹Lambda,同時,會介紹函數(shù)式接口。
引言
Lambda表達式,也可以稱作閉包。它是推動Jdk8發(fā)布的最重要的特性。在此之前,jdk是不允許將函數(shù)作為參數(shù)進行傳遞的,即不支持函數(shù)式編程。這使其在很多地方,代碼過于啰嗦,如同老太太的叨叨。因此,在Jdk8引入Lambda后,代碼變得更加簡潔,更加清晰了。
Lambda語法
完整的Lambda表達式包含三部分組成:參數(shù)列表,箭頭,聲明語句。
(Type1 param1, Type2 param2, ..., TypeN paramN)-> {聲明1; 聲明2; ...... return mentmentM;}
以下是Lambda表達是的重要說明:
1,參數(shù)可選類型聲明:不需要指明param1,param2的具體類型,編譯器可以自動識別。于是,上面可以簡化為:
(param1, param2, ..., paramN)-> {指令1; 指令2; ......}
2,參數(shù)可選圓括號:當參數(shù)僅有1個的時候,圓括號可以省略。于是,上面可以繼續(xù)簡化為:
param-> {指令1; 指令2; ......}
3,函數(shù)體可選大括號:當函數(shù)體中僅包含一個指令的時候,大括號可以省略。
param-> 指令1;
具體表達式示例
// 1.不需要的參數(shù),返回變量5
() -> 5
// 2.接收一個參數(shù)(數(shù)字類型),返回其2倍的值
x -> 2 * x
// 3.接受2個參數(shù)(數(shù)字),并返回他們的差值
(x,y) -> x-y
// 4.接收2個int型整數(shù),返回他們的和
(int x, int y) -> x + y
// 5.接受一個字符串對象,并在控制臺打印,不返回任何值(看起來像是返回void)
(String s)-> System.out.print(s)
函數(shù)式接口
要想使用Lambda,必然需要一個函數(shù)式接口。那么,什么是函數(shù)式接口呢?函數(shù)式接口,F(xiàn)unctional Interface,必須有且僅有一個抽象方法的接口,對默認方法的個數(shù)沒有限制(Jdk8 允許在接口中有默認實現(xiàn)),同時函數(shù)式接口用@FunctionalInterface來標注。函數(shù)式接口可以友好的支持Lambda。
Jdk8 之前符合函數(shù)式接口條件的接口有:
- 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
Jdk8 新增的函數(shù)接口一般都存儲在java.util.function中,其中有很多類,這里不一一贅述。感興趣的同學(xué),可以在這個package中自學(xué)一下。接下來,我們舉兩個例子,來說明函數(shù)式接口與Lambda的用法。
public class LambdaDemo {
public static void main(String[] args) {
runDemo();
}
public static void runDemo() {
//傳統(tǒng)定義
Runnable runnableJdk7 = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread());
}
};
runnableJdk7.run();
//Lambda 形式
Runnable runnableJdk8 = () -> System.out.println(Thread.currentThread());
runnableJdk8.run();
}
}
上述代碼,以定義一個線程為例,展示了Jdk8以前以及Jdk8的做法。那么,Lambda究竟做了什么呢?很明顯,簡化了接口抽象方法的實現(xiàn),我們用Lambda實現(xiàn)了Runnable的 run方法,并創(chuàng)建了一個對象runnableJdk8。這就是函數(shù)式接口必須有且只能有一個抽象方法的原因。因為創(chuàng)建的時候,等號右面必須有,且只能有一個Lambda表達式,這個Lambda表達式,就是用來實現(xiàn)這個函數(shù)式接口唯一的抽象方法。
我們再舉一個例子,假設(shè)有一張學(xué)生成績表,篩選成績大于80的。該怎么寫呢?
public static void score() {
List<Student> listJdk7 = new ArrayList<>(
Arrays.asList(
new Student(1, "張三", 41),
new Student(2, "王二", 92),
new Student(3, "李四", 52),
new Student(4, "王五", 81),
new Student(5, "趙六", 67)
)
);
Iterator<Student> listIt = listJdk7.iterator();
while (listIt.hasNext()) {
Student next = listIt.next();
if (next.getScore().intValue() < 80) {
listIt.remove();
}
}
System.out.println(JSON.toJSONString(listJdk7, SerializerFeature.PrettyFormat));
//以下是Jdk8 Lambda 結(jié)合 函數(shù)式接口
List<Student> listJdk8 = new ArrayList<>(
Arrays.asList(
new Student(1, "張三", 41),
new Student(2, "王二", 92),
new Student(3, "李四", 52),
new Student(4, "王五", 81),
new Student(5, "趙六", 67)
)
);
List<Student> collect = listJdk8.stream().filter(x -> x.getScore() > 80).collect(Collectors.toList());
System.out.println(JSON.toJSONString(collect, SerializerFeature.PrettyFormat));
}
Jdk7這里不再解釋,這里解釋一下Jdk8的實現(xiàn)。Jdk8調(diào)用了Stream的實現(xiàn)類java.util.stream.ReferencePipeline<P_IN, P_OUT>的filter方法,具體實現(xiàn)如下。
@Override
public final Stream<P_OUT> filter(Predicate<? super P_OUT> predicate) {
Objects.requireNonNull(predicate);
return new StatelessOp<P_OUT, P_OUT>(this, StreamShape.REFERENCE,
StreamOpFlag.NOT_SIZED) {
@Override
Sink<P_OUT> opWrapSink(int flags, Sink<P_OUT> sink) {
return new Sink.ChainedReference<P_OUT, P_OUT>(sink) {
@Override
public void begin(long size) {
downstream.begin(-1);
}
@Override
public void accept(P_OUT u) {
if (predicate.test(u))
downstream.accept(u);
}
};
}
};
}
從實現(xiàn)中,我們可以看到,這個方法,傳入一個java.util.function.Predicate的接口函數(shù),并且調(diào)用了其抽象方法test(T t)。該接口的內(nèi)容大致如下,其中抽象方法test,接受一個輸入對象,返回ture或者false。額,我覺得這有點廢話,畢竟代碼是這么寫的。(#.#)
@FunctionalInterface
public interface Predicate<T> {
/**
* Evaluates this predicate on the given argument.
*
* @param t the input argument
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
boolean test(T t);
...
}
我們剛才的寫法是
x -> x.getScore() > 80
x 代表我們要操作的對象,也就是test方法的入?yún)?code>t。而x.getScore()>80則是test方法的具體實現(xiàn)。這個實現(xiàn)會返回boolean值。這樣是不是好理解了。
總結(jié)
借助函數(shù)式接口與接口默認方法,可以使代碼變得更簡潔,更快速。
那么,要來一杯咖啡嗎?
附錄:Jdk8 自帶的函數(shù)式接口
| 序號 | 接口 & 描述 |
|---|---|
| 1 | BiConsumer<T,U>代表了一個接受兩個輸入?yún)?shù)的操作,并且不返回任何結(jié)果 |
| 2 | BiFunction<T,U,R>代表了一個接受兩個輸入?yún)?shù)的方法,并且返回一個結(jié)果 |
| 3 | BinaryOperator<T>代表了一個作用于于兩個同類型操作符的操作,并且返回了操作符同類型的結(jié)果 |
| 4 | BiPredicate<T,U>代表了一個兩個參數(shù)的boolean值方法 |
| 5 | BooleanSupplier代表了boolean值結(jié)果的提供方 |
| 6 | Consumer<T>代表了接受一個輸入?yún)?shù)并且無返回的操作 |
| 7 | DoubleBinaryOperator代表了作用于兩個double值操作符的操作,并且返回了一個double值的結(jié)果。 |
| 8 | DoubleConsumer代表一個接受double值參數(shù)的操作,并且不返回結(jié)果。 |
| 9 | DoubleFunction<R>代表接受一個double值參數(shù)的方法,并且返回結(jié)果 |
| 10 | DoublePredicate代表一個擁有double值參數(shù)的boolean值方法 |
| 11 | DoubleSupplier代表一個double值結(jié)構(gòu)的提供方 |
| 12 | DoubleToIntFunction接受一個double類型輸入,返回一個int類型結(jié)果。 |
| 13 | DoubleToLongFunction接受一個double類型輸入,返回一個long類型結(jié)果 |
| 14 | DoubleUnaryOperator接受一個參數(shù)同為類型double,返回值類型也為double 。 |
| 15 | Function<T,R>接受一個輸入?yún)?shù),返回一個結(jié)果。 |
| 16 | IntBinaryOperator接受兩個參數(shù)同為類型int,返回值類型也為int 。 |
| 17 | IntConsumer接受一個int類型的輸入?yún)?shù),無返回值 。 |
| 18 | IntFunction<R>接受一個int類型輸入?yún)?shù),返回一個結(jié)果 。 |
| 19 | IntPredicate:接受一個int輸入?yún)?shù),返回一個布爾值的結(jié)果。 |
| 20 | IntSupplier無參數(shù),返回一個int類型結(jié)果。 |
| 21 | IntToDoubleFunction接受一個int類型輸入,返回一個double類型結(jié)果 。 |
| 22 | IntToLongFunction接受一個int類型輸入,返回一個long類型結(jié)果。 |
| 23 | IntUnaryOperator接受一個參數(shù)同為類型int,返回值類型也為int 。 |
| 24 | LongBinaryOperator接受兩個參數(shù)同為類型long,返回值類型也為long。 |
| 25 | LongConsumer接受一個long類型的輸入?yún)?shù),無返回值。 |
| 26 | LongFunction<R>接受一個long類型輸入?yún)?shù),返回一個結(jié)果。 |
| 27 | LongPredicateR接受一個long輸入?yún)?shù),返回一個布爾值類型結(jié)果。 |
| 28 | LongSupplier無參數(shù),返回一個結(jié)果long類型的值。 |
| 29 | LongToDoubleFunction接受一個long類型輸入,返回一個double類型結(jié)果。 |
| 30 | LongToIntFunction接受一個long類型輸入,返回一個int類型結(jié)果。 |
| 31 | LongUnaryOperator接受一個參數(shù)同為類型long,返回值類型也為long。 |
| 32 | ObjDoubleConsumer<T>接受一個object類型和一個double類型的輸入?yún)?shù),無返回值。 |
| 33 | ObjIntConsumer<T>接受一個object類型和一個int類型的輸入?yún)?shù),無返回值。 |
| 34 | ObjLongConsumer<T>接受一個object類型和一個long類型的輸入?yún)?shù),無返回值。 |
| 35 | Predicate<T>接受一個輸入?yún)?shù),返回一個布爾值結(jié)果。 |
| 36 | Supplier<T>無參數(shù),返回一個結(jié)果。 |
| 37 | ToDoubleBiFunction<T,U>接受兩個輸入?yún)?shù),返回一個double類型結(jié)果 |
| 38 | ToDoubleFunction<T>接受一個輸入?yún)?shù),返回一個double類型結(jié)果 |
| 39 | ToIntBiFunction<T,U>接受兩個輸入?yún)?shù),返回一個int類型結(jié)果。 |
| 40 | ToIntFunction<T>接受一個輸入?yún)?shù),返回一個int類型結(jié)果。 |
| 41 | ToLongBiFunction<T,U>接受兩個輸入?yún)?shù),返回一個long類型結(jié)果。 |
| 42 | ToLongFunction<T>接受一個輸入?yún)?shù),返回一個long類型結(jié)果。 |
| 43 | UnaryOperator<T>接受一個參數(shù)為類型T,返回值類型也為T。 |