JDK8 于2014年發(fā)布至今咸产,已有6個年頭矢否。其中幾個重要的特性,lambda表達式脑溢,函數(shù)式接口僵朗,方法引用,默認方法等屑彻,至今未完全普及验庙。在回到家鄉(xiāng)邯鄲這個三線小城市后,竟然有同事問我社牲,有沒有簡單的統(tǒng)計List數(shù)據(jù)的方法粪薛,頗讓我驚訝。雖然我認為一個對公司有貢獻的程序員搏恤,首先應(yīng)該熟悉業(yè)務(wù)违寿。但是我也擔(dān)心,技術(shù)上落后太多熟空,也會拖累項目的性能藤巢。因此,本著服務(wù)家鄉(xiāng)的想法息罗,梳理一下Java程序員基本的知識點掂咒。與互聯(lián)網(wǎng)項目不同,我更加重視基礎(chǔ)知識。對于微服務(wù)框架绍刮,我認為絕大多數(shù)應(yīng)用糜工,是用不到的。如果為了使用而使用录淡,只會增加團隊不必要的負擔(dān)捌木。閑話不多說,我們開始介紹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。 |