Lambda與函數(shù)式接口

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)了Runnablerun方法,并創(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。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容