1. Lambda、JDK8接口使用、自定義函數接口

JDK8入門

JDK8默認最大的特性應該就是Lambda表達式了吧。先上線幾個Lambda表達式進行體驗一下。
代碼我托管于GitHub社區:https://github.com/WeidanLi/Java-jdk8-demo

準備

為了測試,我們新建一個蘋果,圍繞著蘋果開來展開需求的實現。蘋果具有兩個屬性,一個顏色和一個重量。我們可以通過集合+我們自己的POJO類來實現。
實例是JDK8實戰中的例子,如有侵犯,請告知刪除。

package cn.liweidan.jdk8.pojo;

/**
 * <p>Desciption:</p>
 * CreateTime : 2017/6/2 下午2:55
 * Author : Weidan
 * Version : V1.0
 */
public class Apple {
    private String color;
    private int wight;

    public Apple(String color, int wight) {
        this.color = color;
        this.wight = wight;
    }

    public Apple() {
    }

    public Apple(int wight) {
        this.wight = wight;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public int getWight() {
        return wight;
    }

    public void setWight(int wight) {
        this.wight = wight;
    }

    @Override
    public String toString() {
        return "Apple{" +
                "color='" + color + '\'' +
                ", wight=" + wight +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Apple apple = (Apple) o;

        if (wight != apple.wight) return false;
        return color != null ? color.equals(apple.color) : apple.color == null;
    }

    @Override
    public int hashCode() {
        int result = color != null ? color.hashCode() : 0;
        result = 31 * result + wight;
        return result;
    }
}

一、Predicate接口

Predicate接口中定義了一個需要實現的方法test(T t)方法,還有JDK8特有的幾個已經實現了的默認方法,打開Predicate里有一個注解,@FunctionalInterface,代表這個接口只能擁有一個未實現的方法,是一個函數接口。
現在,我們需要在我們的一個蘋果集合中,拿出重量大于100的蘋果。
如果是以前,那么我們就需要通過遍歷一個集合,拿出來符合條件的蘋果,然后進行返回。

public static List<Apple> filterApple(List<Apple> appleList){
    List<Apple> apples = new ArrayList<>();
    for (Apple apple : appleList) {
        if(apple.getWight() > 100){
            apples.add(apple);
        }
    }
    return apples;
}

這種方式,可以實現現在的需求,但是當用戶的需求改了以后,比如找出紅色的蘋果,那我們就需要重寫里面的if方法的條件。不夠靈活。
JDK8引入Predicate接口以后,我們就可以通過把這個條件進行封裝,把經常改變需求的if里面的判斷語句進行封裝,變成Predicate接口,在調用filterApple的時候將條件傳入,即可實現需求的變更。
JDK8的方法可以這么寫:

/**
 Predicate<T>接口:默認調用test方法,返回boolean值,傳遞的參數只有一個。
 在需要調用Lambeda表達式返回boolean的時候使用。
 */
public static List<Apple> filterApple(List<Apple> appleList, Predicate<Apple> predicate){
    List<Apple> apples = new ArrayList<>();
    for (Apple apple : appleList) {
        if(predicate.test(apple)){
            apples.add(apple);
        }
    }
    return apples;
}

Predicate即可通過Lambda表達式傳入需要的條件,即可實現可變的過濾需求。

public static void main(String[] args) {
    List<Apple> appleList = Arrays.asList(new Apple("RED", 80),
            new Apple("GREEN", 100),
            new Apple("BLACK", 150));
    // 重量大于100
    List<Apple> filterApple = filterApple(appleList, apple -> apple.getWight() > 100);
    // 紅色的蘋果
    filterApple = filterApple(appleList, apple -> apple.getColor().equals("RED"));
}

可以看到,我們已經可以把我們需要的蘋果篩選出來了。
這個方法的調用,就是通過傳入一個Predicate<Apple>實現,然后把apple.getWight() > 100返回給test(Apple apple)方法,test方法再進行返回給if語句,從而拿到我們需要的蘋果,是不是比以前的方法簡單。
所以當我們有個需求是需要可變的返回值boolean得需求的時候,即可使用Predicate接口。后面的JDK8的集合流中的filter()也是通過傳入Predicate拿到boolean值進行篩選的。
代碼位置:cn.liweidan.jdk8.PredicateInterface中

二、Consumer<T>接口

Consumer接口也是個函數式接口,只有該接口只含有一個未實現的accept(T t)方法。返回值是void,用于封裝在日常生活中沒有返回值情況的代碼,比如遍歷一個集合并且打印每個元素,我們即可通過Consumer接口實現一個forEach方法。接收一個集合并且使用Lambda書寫需要對集合的操作,在Consumer參數中即可拿到每一個集合中的元素進行操作。

/**
 * Consumer<T>提供了一個accept方法,返回void類型。
 */
public static <T>void forEach(List<T> list, Consumer<T> s){
    for (T t : list) {
        s.accept(t);
    }
}

調用該forEach方法:

@Test
public void test01(){
    forEach(Arrays.asList("Lambde", "test", "stream"),
            s -> {
                System.out.println(s);
            });
}

Consumer意為消費者,在上面例子中,即拿到集合中所有的元素進行消費。
代碼位置:cn.liweidan.jdk8.ConsumerInterface

三、Function<T, R>接口

Function就好玩了,可以自定義傳入的類型以及傳出的類型。T表示傳入的類型,R表示傳出的類型。
Function接口只有一個未實現的R apply(T t)方法,用于傳入一個T類型的值,而傳出一個R類型的值。比如我們現在需要把一組字符串的長度全部輸出出來。在以往我們就需要通過遍歷所有的元素,然后用一個集合去封裝,在遍歷中一個一個取出長度放入我們新建的集合當中去,然后將這個集合進行返回。
然而使用Function方法的時候,我們就可以很自由的進行取值。
現在我們來寫一個map方法,用于取出集合中元素某一個屬性的所有值并且進行返回。

/**
 * Function<T, R>, T表示傳入的類型,R表示返回的類型。即傳入T類型的參數,返回R類型的參數
 * @param list
 * @param function
 * @param <T>
 * @param <R>
 * @return
 */
public static <T,R> List<R> map(List<T> list, Function<T, R> function){
    List<R> result = new ArrayList<>();
    for (T t : list) {
        result.add(function.apply(t));
    }
    return result;
}

@Test
public void test01(){
    List<String> stringList = Arrays.asList("lambda", "test", "javascript");
    List<Integer> map = map(stringList, s -> s.length());
    System.out.println(map);
}

代碼位置:cn.liweidan.jdk8.FunctionDemo
運行的時候JDK8可以通過傳入的集合的泛型是String屬性,從而把String賦予給T,然后再根據我們需要取值的結果的屬性值,在這里s.length()取出來的值是Integer類型的。所以R的類型就是Integer類型。把T和R帶入Function中的兩個泛型值,我們就可以發現其實就是和我們以前寫的遍歷取值是一樣的,只不過是現在的方式可以更靈活。這里得益于Lambda表達式,通過() -> expression或者() -> {statement;}來表達我們需要的需求。

四、Lambda表達式

Lambda表達式的歷史就不說了。這里說的是Lambda的格式,Lambda表達式的前半部分(Apple a, Apple b)表示傳入給Lambda的參數值,也就是相當于我們方法中的參數。這個不難理解吧。然而后半部分就比較有講究了。
后半部分{}或者直接書寫"result",用于表示返回值。在這里需要注意的是{}"result"的區別,{}表示書寫Java語句,也就是我們日常寫的代碼,比如上面的forEach方法中,我們就是通過s -> {System.out.println(s);}來寫Java代碼的,但是如果我們直接返回值,就不需要寫花括號,只需要直接把返回值放在后部分即可。如Predicate接口中的apple -> apple.getWight() > 100后半部分直接用返回值返回boolean值。像函數式接口都支持Lambda的書寫方式。
比如:
() -> return "result";() -> {"result"}均是錯誤的寫法,應該寫成() -> "result"或者() -> {return "result";}
對于Lambda表達式我是這么去理解的,前半部分就是傳入的值,后半部分就是我們需要執行的語句,函數式接口就相當于把我們需要執行的語句放入Lambda的后半部分一樣。
這時候我們就需要來一個例子看看了,并且講解函數式接口。

五、自定義函數式接口。

我們現在有一個需求,我需要有個方法可以自定義我們讀取文件的內容的方法,但是前后的try-catch均讓代碼幫我封裝了。我只要關心我讀取的代碼就可以了。
這里就涉及到了環繞的函數式編程了,但是這并不難,我們先來看看傳統的方法:

public static String processFileOld(BufferedReaderProcess b){
    StringBuilder str = new StringBuilder();
    try(BufferedReader br =
                new BufferedReader(
                        new FileReader("/Users/liweidan/Java/workspace/Java-jdk8-demo/src/main/java/cn/liweidan/jdk8/Lambda/LambdaDemo01.java"))){
        str.append(br.readLine());
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return str.toString();
}

這樣可以實現讀取指定文件的一行,但是當我們需要讀取文件的第二行的時候怎么辦,我們又需要修改這段代碼。
所以我們應該把讀取的內容給封裝起來。首先我們先建一個函數式接口BufferedReaderProcess接收一個BufferedReader,然后對BufferedReader的動作進行操作。

package cn.liweidan.jdk8.Lambda.Demo02;

import java.io.BufferedReader;
import java.io.IOException;

/**
 * <p>Desciption:讀取流的函數式編程,把行為參數化</p>
 * CreateTime : 2017/6/2 下午3:33
 * Author : Weidan
 * Version : V1.0
 */
@FunctionalInterface // 注意這個注解
public interface BufferedReaderProcess {

    String process(BufferedReader b) throws IOException;

}

@FunctionalInterface注解,用于只是該接口是函數式接口,如果沒有該注釋也可以編譯通過,但是編譯器就不能判斷他只能擁有一個未實現的方法。
這時候我們可以編寫一個方法,用于接收BufferedReaderProcess,前后包裝流的常用動作,包括流的獲取以及流的關閉,這里我們使用JDK7的新特性,自動關閉流。

@Test
public void test01(){
    String br = LambdaDemo02.processFile(b -> b.readLine());
    System.out.println(br);
    br = LambdaDemo02.processFile(b -> b.readLine() + b.readLine() + b.readLine());
    System.out.println(br);
}

/**
 * 將讀取文件的動作封裝成參數
 * @param b
 * @return
 */
public static String processFile(BufferedReaderProcess b){
    try(BufferedReader br =
                new BufferedReader(
                        new FileReader("/Users/liweidan/Java/workspace/Java-jdk8-demo/src/main/java/cn/liweidan/jdk8/Lambda/LambdaDemo01.java"))){
        return b.process(br);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return null;
}

代碼位置:cn.liweidan.jdk8.Lambda.LambdaDemo02

六、Lambda的方法引用

當我們需要使用Lambda表達式去調用一個對象的方法的時候,可以通過::來調用,比如調用String的compareToIgnoreCase方法,可以寫成String:: compareToIgnoreCase,又可以減少代碼量了。方法調用可以針對構造方法普通方法以及靜態方法進行調用。下面給出幾個需求的實現代碼。

public class MethodQuoteDemo01 {

    @Test
    public void test(){
        List<String> list = Arrays.asList("a", "b", "A", "B");
        list.sort(String::compareToIgnoreCase);// 調用compareToIgnoreCase進行比較
        System.out.println(list);
    }

}
package cn.liweidan.jdk8.MethodQuote;

import cn.liweidan.jdk8.pojo.Apple;
import org.junit.Test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;

/**
 * <p>Desciption:構造方法引用Demo</p>
 * CreateTime : 2017/6/2 下午7:46
 * Author : Weidan
 * Version : V1.0
 */
public class MethodQuoteDemo02 {

    @Test
    public void test01(){
        /**
         * 空構造器
         */
        Supplier<Apple> c1 = Apple::new;
        Apple apple = c1.get();
        System.out.println(apple);
    }

    @Test
    public void test02(){
        /**
         * Apple存在只需要傳遞一個Integer參數的構造器
         */
        Function<Integer, Apple> c2 = Apple::new;
        Apple apply = c2.apply(100);
        System.out.println(apply);
    }

    /**
     * 使用map批量創建指定重量的蘋果
     */
    @Test
    public void test03(){
        List<Integer> integers = Arrays.asList(7, 3, 9, 10);
        List<Apple> appleList = map(integers, Apple::new);
        System.out.println(appleList);
    }
    public static <T, R>List<R> map(List<T> wights, Function<T, R> function){
        List<R> res = new ArrayList<>();
        for (T t : wights) {
            res.add(function.apply(t));
        }
        return res;
    }

    /**
     * 調用getWight對蘋果進行按照重量進行排序
     */
    @Test
    public void test04(){
        List<Apple> appleList = Arrays.asList(new Apple("RED", 80),
                new Apple("GREEN", 100),
                new Apple("BLACK", 150));
        appleList.sort(Comparator.comparing(Apple::getWight));
        System.out.println(appleList);
    }

}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容