java中不太常見的東西(2) - Lambda表達式

引言

在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版本的新特性哦。如果大家有什麼問題可以在下方留言交流,共同學習進步。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章