Lambda學習總結(一)--函數式接口


Lambda 表達式是 JDK 1.8 里面的一個重要更新,這意味着 Java 也開始承認了函數式編程,並且嘗試引入其中,我們今天就來了解下它的使用。

一、函數式接口

1.1 概念

函數式接口在 Java 中是指:有且僅有一個抽象方法的接口

1.2 格式

修飾符 interface 接口名稱 {
    返回值類型 方法名稱(可選參數信息);
    // 其他非抽象方法內容
}

例如:

public interface MyFunctionalInterface {
    void myMethod();
}

1.3 @FunctionalInterface 注解

@Override 注解的作用類似,為了保證接口 有且僅有一個抽象方法,Java 8 中專門為其引入了一個新的注解: @FunctionalInterface

@FunctionalInterface
public interface MyFunctionalInterface {
    void myMethod();
}

注意:即使不使用該注解,只要滿足函數式接口的定義也認為該接口是一個函數式接口不影響其使用。

1.4 自定義函數式接口的使用

自定義函數式接口的典型使用場景就是作為方法的參數,例如:

public class DemoFunctionalInterface {
    // 使用自定義的函數式接口作為方法參數
    private static void print(MyFunctionalInterface inter) {
        // 使用自定義的函數式接口方法
        inter.myMethod();
    }
    
    public static void main(String[] args) {
        // 調用方法
        print(() -> System.out.println("執行 Lambda 函數方法"));
    }
}

二、函數式編程

2.1 Lambda 延遲執行

在講 Lambda 延遲執行 之前,我們先來看一個日志打印的案例:

public class DemoLogger {
    
    private static void log(int lever,String msg) {
        if(lever == 1){
            System.out.println(msg);
        }
    }
    
    public static void main(String[] args) {
        String msgA = "Hello";
        String msgB = "World";
        String msgC = "Java";
        log(1,msgA + msgB + msgC);
    }
}

上面的案例存在一個問題,無論日志級別是否滿足要求,作為 log 方法的第二個參數,三個字符串一定會先被拼接然后傳入方法內。如果級別不滿足條件的話,字符串的拼接操作就白做了,存在着性能上的浪費。而 Lambda 表達式是延遲執行的,正好可以作為解決方案,用以提升性能。

@FunctionalInterface
public interface MessageBuilder {  
    String builderMessage();    
}

public class DemoLogger {

    private static void log(int lever,MessageBuilder builder) {
        if(lever == 1){
            System.out.println(builder.builderMessage());
        }
    }

    public static void main(String[] args) {
        String msgA = "Hello";
        String msgB = "World";
        String msgC = "Java";

        log(1,() -> {
            System.out.println("級別為1時執行");
            return msgA+msgB+msgC;
        });

        log(2,() -> {
            System.out.println("級別不為1時執行");
            return msgA+msgB+msgC;
        });
    }

}

運行程序,我們來看下控制台的輸出結果:

級別為1時執行
HelloWorldJava

從輸出結果上可以看出,如果不符合日志級別要求的話,Lambda 將不會被執行,進而達到節省性能的效果。

2.2 使用 Lambda 作為參數

向上面的日志打印案例實際上就是將 Lambda 當作一個參數來使用,只要方法的參數是一個函數式接口類型,那么就可以使用 Lambda 表達式進行替代。例如 Runnable 接口就是一個函數式接口:

public class DemoParameter {

    private static void startRunnable(Runnable task) {
        new Thread(task).start();
    }

    public static void main(String[] args) {
        startRunnable(() -> System.out.println("線程執行中"));
    }
}

2.3使用 Lambda 作為返回值

同理,如果一個方法的返回值類型是一個函數式接口,那么就可以直接返回一個 Lambda 表達式:

public class DemoResult {
    private static Comparator<Integer> newComparator() {
        return (a, b) -> b - a;
    }

    public static void main(String[] args) {
        Integer[] array = new Integer[]{0, -5, 33, 100, 88, -40};
        // 排序前
        System.out.println(Arrays.toString(array));
        Arrays.sort(array,newComparator());
        // 排序后
        System.out.println(Arrays.toString(array));
    }

}

三、Lambda 四大核心函數式接口

除了我們自己定義函數式接口外,JDK 也提供了大量的常用函數式接口用以豐富 Lambda 的使用場景,它們主要在 java.util.function 包中被提供。

3.1 Supplier 接口

java.util.function.Supplier<T> 接口僅包含一個無參方法: T get()。用來獲取一個泛型參數指定類型的對象數據。

public class DemoSupplier {

    private static String getString(Supplier<String> supplier) {
        return supplier.get();
    }

    public static void main(String[] args) {
        String msgA = "Hello ";
        String msgB = "World ";
        System.out.println(getString(() -> msgA + msgB));
    }

}

3.2 Consumer 接口

java.util.function.Consumer<T> 接口與 Supplier 接口正好相反,它不是用於生產一個數據,而是消費一個數據,它的數據類型由泛型決定。

Consumer 接口有兩個方法:

方法名稱 方法類型 方法含義
accept 抽象方法 用於消費一個數據
andThen 默認方法 組合消費一個數據

3.2.1 accept 方法

public class DemoConsumer {

    private static void consumerString(Consumer<String> one){
        one.accept("Hello");
    }

    public static void main(String[] args) {
        consumerString((s)->System.out.println(s));
    }
    
}

3.2.2 andThen 方法

public class DemoConsumer {

    private static void consumerString(Consumer<String> one,Consumer<String> two){
        one.andThen(two).accept("Hello");
    }

    public static void main(String[] args) {
        consumerString(
                (s)->System.out.println(s.toUpperCase()),
                (s)->System.out.println(s.toLowerCase())
        );
    }

}

3.3 Predicate 接口

當我們需要對某種類型的數據進行判斷,從而得到一個布爾值類型的結果的時候,可以使用java.util.function.Predicate 接口。

Predicate 接口有如下方法:

方法名稱 方法類型 方法含義
test 抽象方法 用於條件判斷
and 默認方法 組合條件判斷,表示 與 關系
or 默認方法 組合條件判斷,表示 或 關系
negate 默認方法 組合條件判斷,表示 非 關系

3.3.1 test 方法

條件判斷的條件是根據傳入的 Lambda 表達式邏輯 ,下面的示例的判斷邏輯是傳入的值是否大於0。

public class DemoTest {

    private static void method(Predicate<Integer> predicate) {
        boolean result = predicate.test(5);
        System.out.println(result);
    }

    public static void main(String[] args) {
        method((s) -> s > 0);
    }

}

3.3.2 and 方法

如果要判斷字符串既包含字母 a 又要包含字母 e,那么可以使用 and 方法:

public class DemoAnd {
    private static void method(Predicate<String> one,Predicate<String> two) {
        boolean result = one.and(two).test("abcd");
        System.out.println(result);
    }

    public static void main(String[] args) {
        method((s) -> s.contains("a"),(s) -> s.contains("e"));
    }

}

3.3.3 or 方法

如果只需要滿足一個條件即可,那就可以使用 or 方法:

public class DemoOr {
    private static void method(Predicate<String> one,Predicate<String> two) {
        boolean result = one.or(two).test("abcd");
        System.out.println(result);
    }

    public static void main(String[] args) {
        method((s) -> s.contains("a"),(s) -> s.contains("e"));
    }

}

3.3.4 negate 方法

剩下的就只有取反的邏輯了,判斷值不能大於0:

public class DemoNegate {

    private static void method(Predicate<Integer> predicate) {
        boolean result = predicate.negate().test(5);
        System.out.println(result);
    }

    public static void main(String[] args) {
        method((s) -> s > 0);
    }

}

3.4 Function 接口

java.util.function.Function<T,R> 接口用來根據一個類型的數據得到另一個類型的數據,前者稱為前置條件,后者稱為后置條件。

方法名稱 方法類型 方法含義
apply(T t) 抽象方法 根據類型 T 參數獲取類型 R 的結果
andThen 默認方法 用來進行組合操作

3.4.1 apply 方法

使用場景如:將 String 類型轉換為 Integer 類型:

public class DemoFunctionApply {

    private static void method(Function<String,Integer> function){
        Integer apply = function.apply("10");
        System.out.println(apply + 0);
    }

    public static void main(String[] args) {
        method(s -> Integer.parseInt(s));
    }
}

3.4.2 andThen 方法

public class DemoFunctionAndThen {

    private static void method(Function<String,Integer> one,
                               Function<Integer,Integer> two){
        Integer apply = one.andThen(two).apply("10");
        System.out.println(apply + 0);
    }

    public static void main(String[] args) {
        method(s -> Integer.parseInt(s),i -> i *= 10);
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM