Java基礎篇(05):函數式編程概念和應用

本文源碼:GitHub·點這裏 || GitEE·點這裏java

1、函數式概念

函數式編程是一種結構化編程的範式,主要思想是把運算過程儘可能寫成系列嵌套的函數調用。函數編程的概念表述帶有很抽象的感受,能夠基於案例看:c++

public class Function01 {
    public static void main(String[] args) {
        // 運算:(x+y)* c
        int x1 = 2 ;
        int y1 = 3 ;
        int c1 = 4 ;
        int sum1 = x1 + y1 ;
        int res1 = sum1 * c1 ;
        System.out.println("res1 = "+res1);
    }
}

這裏基於過程的方式作計算,上面的代碼塊着重在描述程序執行過程。git

在看基於函數的方式解決方法:github

public class Function02 {
    public static void main(String[] args) {
        // 函數式計算
        System.out.println("func01 = "+func01(2,3,4));
    }
    private static int func01 (int x,int y,int c){
        return (x+y)*c;
    }
}

函數式編程的核心要素:傳入參數,執行邏輯,返回值,也能夠沒有返回值。算法

函數式的編程風格側重描述程序的執行邏輯,不是執行過程。數據庫

同上面計算過程相比,函數式編程也減小不少臨時變量的建立,代碼風格也變的簡潔清楚。編程

2、函數與方法

在Java語言中有函數式編程風格,可是Java代碼中沒有函數的說法,而是稱爲:方法;設計模式

public class Function03 {
    public static void main(String[] args) {
        Func03 func03 = new Func03();
        func03.add(2);
        System.out.println(func03.res1);
    }
}
class Func03 {
    public int res1 = 0 ;
    public void add (int a1){
        this.res1 = a1 +1 ;
    }
}

類定義引用數據類型,類實例化後的對象能夠調用類內部的方法和數據,這是最直觀的感受。閉包

可是方法又有靜態和非靜態的區別,靜態方法屬於類全部,類實例化前便可使用。架構

非靜態方法能夠訪問類中的任何成員變量和方法,而且必須是類實例化後的對象才能夠調用。

3、JDK函數基礎

一、Lambda表達式

Lambda表達式也可稱爲閉包,是推進Java8發佈的最重要新特性,容許把函數做爲一個方法的參數(函數做爲參數傳遞進方法中)。

這裏就很鮮明的對比Lambda表達式語法和傳統用法。

public class Lambda01 {
    interface LambdaOpera {
        int operation(int a, int b);
    }
    public static void main(String[] args) {
        LambdaOpera lambdaOpera = new LambdaOpera(){
            @Override
            public int operation(int a, int b) {
                return a * b ;
            }
        };
        System.out.println(lambdaOpera.operation(3,2));
        LambdaOpera lambdaOpera01 = (int a, int b) -> a + b;
        LambdaOpera lambdaOpera02 = (int a, int b) -> a - b;
        System.out.println(lambdaOpera01.operation(3,2));
        System.out.println(lambdaOpera02.operation(3,2));
    }
}

在看一個直觀的應用案例,基於Lambda的方式建立線程,可使代碼變的更加簡潔緊湊:

public class Lambda02 {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 2; i++) {
                    System.out.println(i);
                }
            }
        }).start();
        // 對比 Lambda 方式
        new Thread(() -> {
            for (int i = 0; i < 2; i++) {
                System.out.println(i);
            }
        }).start();
    }
}

在看一下Runnable接口的結構:

FunctionalInterface標記在接口上,表示該接口是函數式接口,而且該接口只包含一個抽象方法,

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

Lambda表達式自己能夠理解爲就是一個接口的實現過程,這裏runnable就是完整的Lambda表達式聲明:

public class Lambda04 {
    public static void main(String[] args) {
        Runnable runnable = () -> {
            System.out.println("run one...");
        };
        Thread thread = new Thread(runnable);
        thread.start();
    }
}

Lambda表達式最直觀的做用就是使得代碼變得異常簡潔,而且能夠做爲參數傳遞。

二、函數式接口

Lambda表達式雖然有不少優勢,可是使用的時候須要定義一些接口用來完成編碼,這樣又使得表達式又變得重量級,Java8自身已經提供幾個常見的函數式接口。

  • Function:輸入一個參數,返回一個結果;
  • Consumer:輸入一個參數,不返回結果;
  • BiFunction:輸入兩個參數,返回一個結果;
  • BiConsumer:輸入兩個參數,不返回任何結果;
public class Lambda05 {
    public static void main(String[] args) {
        Function<Integer, Integer> function01 = x -> x * 2;
        System.out.println(function01.apply(2));
        BiFunction<Integer, Integer, Integer> function02 = (x, y) -> x * y;
        System.out.println(function02.apply(2, 3));

        Consumer<String> consumer01 = msg -> System.out.println("msg:"+msg);
        consumer01.accept("hello");

        BiConsumer<String,Integer> consumer02 = (msg,i)
                -> System.out.println(msg+":"+i);
        consumer02.accept("world",3);
    }
}

若是面對更復雜的業務需求,能夠自定義函數式接口去解決。

4、Optional類

一、Null判斷

Optional類是Java函數式編程的應用,主要用來解決常見的空指針異常問題。

在Java編程的開發中,不少地方都能常見空指針異常的拋出,若是想避免這個問題就要加入不少判斷:

public class Optional01 {
    public static void main(String[] args) {
        User user = new User(1,"hello") ;
        if (user != null){
            if (user.getName() != null){
                System.out.println(user.getName());
            }
        }
    }
}

爲了確保程序不拋出空指針這種低級的錯誤,在程序中隨處能夠null的判斷,代碼顯然冗餘和繁雜。

二、Optional應用

基於Optional類建立的對象可能包含空值和null值,也一樣會拋出對應的異常:

public class Optional02 {
    public static void main(String[] args) {
        // NoSuchElementException
        Optional<User> optionalUser = Optional.empty();
        optionalUser.get();
        // NullPointerException
        Optional<User> nullOpt = Optional.of(null);
        nullOpt.get();
    }
}

因此在不明確對象的具體狀況下,使用ofNullable()方法:

public class Optional03 {
    public static void main(String[] args) {
        User user = new User(1,"say");
        Optional<User> optionalUser = Optional.ofNullable(user);
        if (optionalUser.isPresent()){
            System.out.println(optionalUser.get().getName());
        }
        User user1 = null ;
        User createUser = Optional.ofNullable(user1).orElse(createUser());
        System.out.println(createUser.getName());
        User user2 = null ;
        Optional.ofNullable(user2).orElseThrow( ()
                -> new RuntimeException());;
    }
    public static User createUser (){
        return new User(2,"hello") ;
    }
}

這樣看下來Optional結合鏈式方法和Lambda表達式就很大程度上簡化了應用的代碼量:

public class Optional04 {
    public static void main(String[] args) {
        // 一、map轉換方法
        User user = new User(99, "Java");
        // user = null ;
        String name = Optional.ofNullable(user)
                .map(u -> u.getName()).orElse("c++");
        System.out.println(name);
        // 二、過濾方法
        Optional<User> optUser01 = Optional.ofNullable(user)
                .filter(u -> u.getName() != null && u.getName().contains("c++"));
        // NoSuchElementException
        System.out.println(optUser01.get().getName());
    }
}

Optional提供null處理的各類方法,能夠簡潔不少代碼判斷,可是在使用風格上和以前變化很大。

5、Stream流

若是Optional簡化不少Null的判斷,那Stream流的API則簡化了不少集合的遍歷判斷,一樣也是基於函數式編程。

Java基礎篇(05):函數式編程概念和應用

上述爲Stream接口繼承關係如圖,一樣提供一些特定接口和較大的包裝接口,經過源碼查看,能夠看到和函數編程也是密切相關。

public class Stream01 {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("hello", "java");
        stream.forEach(str -> System.out.print(str+";"));
    }
}

Stream與函數接口結合使用,函數接口又可使用Lambda表達式進行簡化代碼。在Java8經過Stream能夠大量簡化集合使用的代碼複雜度。

public class Stream02 {
    public static void main(String[] args) {
        // 一、轉換Stream
        List<String> list = Arrays.asList("java+;", "c++;", "net;");
        list.stream();
        // 二、forEach操做
        list.stream().forEach(System.out::print);
        // 三、map映射,輸出 3,4
        IntStream.rangeClosed(2,3).map(x->x+1).forEach(System.out::println);
        // 四、filter過濾
        list.stream().filter(str -> str.contains("+")).forEach(System.out::print);
        // 五、distinct去重
        Integer[] arr = new Integer[]{3, 1, 3, 1, 2,4};
        Stream.of(arr).distinct().forEach(System.out::println);
        // 六、sorted排序
        Stream.of(arr).sorted().forEach(System.out::println);
        // 七、collect轉換
        List<String> newList = list.stream().filter(str -> str.contains("+"))
                .collect(Collectors.toList());
        newList.stream().forEach(System.out::print);
    }
}

在沒有Stream相關API以前,對於集合的操做和遍歷都會產生大量的代碼,經過Stream相關API集合的函數式編程和Lambda表達式的風格,簡化集合不少操做。

6、源代碼地址

GitHub·地址
https://github.com/cicadasmile/java-base-parent
GitEE·地址
https://gitee.com/cicadasmile/java-base-parent

閱讀標籤

Java基礎】【設計模式】【結構與算法】【Linux系統】【數據庫

分佈式架構】【微服務】【大數據組件】【SpringBoot進階】【Spring&Boot基礎

數據分析】【技術導圖】【 職場

Java基礎篇(05):函數式編程概念和應用

相關文章
相關標籤/搜索