lambda與java8函數式編程

lambda表達式

Lambda表達式的基本語法::(parameters) -> expression 或 (parameters) ->{ statements; }

  • () -> 代表了 lambda的一個表達式
  • 單行代碼無需寫return (無論函數式接口有沒有返回值),花括號
  • 多行代碼必須寫花括號,有返回值的一定要寫返回值
  • 單行代碼且有參數的情況下可以不寫 () 如 s->System.out.println(s)
  • (T t)中的參數類型可寫可不寫

例子

// 1. 不需要參數,返回值爲 5  
() -> 5  
  
// 2. 接收一個參數(數字類型),返回其2倍的值  
x -> 2 * x  
  
// 3. 接受2個參數(數字),並返回他們的差值  
(x, y) -> x – y  
  
// 4. 接收2個int型整數,返回他們的和  
(int x, int y) -> x + y  
  
// 5. 接受一個 string 對象,並在控制檯打印,不返回任何值(看起來像是返回void)  
(String s) -> System.out.print(s)  

主要使用場景

1. 簡化匿名類的編碼
2. 減少不必要的方法創建
3. 事件處理
4. stream中使用

函數式編程

函數式接口

1 .概念

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

函數式接口,即適用於函數式編程場景的接口。而java中的函數式編程體現就是Lambda,所以函數式接口就是可以適用於Lambda使用的接口。只有確保接口中有且僅有一個抽象方法,Java中的Lambda才能順利地進行推導。

備註:“語法糖"是指使用更加方便,但是原理不變的代碼語法。例如在遍歷集合時使用的for-each語法,其實底層的實現原理仍然是迭代器,這便是“語法糖”。從應用層面來講,Java中的Lambda可以被當做是匿名內部類的“語法糖”,但是二者在原理上是不同的。

2. 格式

接口中只能存在一個抽象方法

    @FunctionalInterface
    interface Callback {
        void callback();
    }

3.@FunctionalInterface註解

一旦使用該註解來定義接口,編譯器將會強制檢查該接口是否確實有且僅有一個抽象方法,否則將會報錯。需要注意的是,即使不使用該註解,只要滿足函數式接口的定義,這仍然是一個函數式接口,使用起來都一樣。(該接口是一個標記接口)

  • 被@FunctionalInterface註釋的接口,滿足函數式接口的約束。
  • 沒有被@FunctionalInterface註釋的接口,但是滿足函數式接口的約束。

函數式的約束:  

  • 接口有且只能有個一個抽象方法,只有方法定義,沒有方法體  。
  • 在接口中覆寫Object類中的public方法,不算是函數式接口的方法。
  • 在接口中的default方法,不算是函數式接口的方法。
  • 在接口中的static方法,不算是函數式接口的方法。

常用函數式接口

Consumer<T>:消費型接口

代表了接受一個輸入參數並且無返回的操作

        Consumer<String> testConsumer = param -> System.out.println(param);
        testConsumer.accept("testConsumer");

Consumer默認提供了andThen

作用:用於連接兩個Consumer接口,一個是調用andThen方法的Consumer接口this,一個是andThen方法的參數after。

con1.andThen(con2).accept(s);等價於con1.accept(s);con2.accept(s);

Supplier<T>:供給型接口

無參數,返回一個結果。

        Supplier<String> testSupplier = () -> String.valueOf(Math.random());
        System.out.println(testSupplier.get());

Function<T, R>:函數型接口

接受一個輸入參數,返回一個結果

        Function<Integer, Integer> testFunction = s -> s * 2;
        System.out.println(testFunction.apply(6));

默認提供的一些方法

Predicate<T>:斷言型接口

 接受一個輸入參數,返回一個布爾值結果。

        Predicate<String> testPredicate = s -> s.equals("test");
        System.out.println(testPredicate.test("test"));

默認提供的一些方法

注意:

  • and方法與邏輯運算符&&功能相同
  • or方法與邏輯運算符||功能相同
  • negate方法與邏輯運算符!功能相同

每種接口都有一些默認的方法,可以根據業務需求組合出更多的效果

擴展函數接口

參數個數上擴展:

比如接收雙參數的,有 Bi 前綴, 比如 BiConsumer<T,U>, BiFunction<T,U,R> ;

特殊常用的變形:

比如 BinaryOperator , 是同類型的雙參數 BiFunction<T,T,T> ,二元操作符 ; UnaryOperator 是 Function<T,T> 一元操作符。

類型上擴展:

比如接收原子類型參數的,比如 [Int|Double|Long] [Function|Consumer|Supplier|Predicate]

爲什麼要有基本類型擴展

只有對象類型才能作爲泛型參數,對於基本類型就涉及到裝箱拆箱的操作,雖然是自動的

但是這不可避免給內存帶來了額外的開銷,裝箱和拆箱都會帶來開銷

所以爲了減小這些性能開銷   對基本類型進行類型擴展

Stream 類的某些方法對基本類型和裝箱類型做了區分

Java 8中,僅對 整型、長整型和雙浮點型做了特殊處理  因爲它們在數值計算中用得最多

對基本類型做特殊處理的方法在命名上有明確的規範

  • 如果參數是基本類型,則不加前綴只需類型名即可
  • 如果方法返回類型爲基本類型,則在基本類型前再加上一個 To

總結一句話:加了類型前綴[Int|Double|Long] 表示參數是基本類型, 如果在此基礎上又加上了To  表示返回類型是基本類型

如有可能,應儘可能多地使用對基本類型做過特殊處理的方法,進而改善性能

其它

方法引用

方法引用可以在某些條件成立的情況下,更加簡化lambda表達式的聲明。方法引用語法格式有以下三種:

  • objectName::instanceMethod
  • ClassName::staticMethod
  • ClassName::instanceMethod

前兩種方式類似,等同於把 lambda 表達式的參數直接當成 instanceMethod/staticMethod 的參數來調用。比如 System.out::println 等同於 x->System.out.println(x);Math::max 等同於 (x, y)->Math.max(x,y) 。

最後一種方式,等同於把lambda表達式的第一個參數當成 instanceMethod 的目標對象,其他剩餘參數當成該方法的參數。比如 String::toLowerCase 等同於 x -> x.toLowerCase()。

//Funciton
Lambda表達式: (Apple a) -> a.getWeight()
等價的方法引用: Apple::getWeight
//Conusmer
Lambda表達式: () -> Thread.currentThread().dumpStack()
等價的方法引用: Thread.currentThread()::dumpStack
//BiFunction
Lambda表達式: (str, i) -> str.substring(i)
等價的方法引用: String::substring
//Function
Lambda表達式: (String s) -> System.out.println(s)
等價的方法引用: System.out::printl

只有當lambda表達式的體只調用一個方法而不做其他操作時,纔可以把lambda表達式重寫爲方法引用。如以下表達式:

s -> s.length == 0

這裏有一個方法調用,但是還有一個比較,因而這裏不能使用方法引用。

構造器引用

構造器引用語法如下:ClassName::new,把lambda表達式的參數當成ClassName構造器的參數 。例如BigDecimal::new等同於x->new BigDecimal(x)。3

//無參構造函數
Supplier<Apple> c1 = () -> new Apple();
Supplier<Apple> c1 = Apple::new;
//一元構造函數
Function<Integer, Apple> c2 = (weight) -> new Apple(weight);
Function<Integer, Apple> c2 = Apple::new;
//二元構造函數
BiFunction<Integer, String, Apple> c3 =(weight, color) -> new Apple(weight, color);
BiFunction<Integer, String, Apple> c3 = Apple::new

Lambda表達式和匿名內部類的區別

一、所需類型不同:

  • 匿名內部類可以是:接口、抽象類、或者具體類
  • Lambda 表達式只能是:接口

二、使用限制不同:

  • 匿名內部類:接口中可以多個或者一個方法
  • Lambda 表達式要求:接口中的只能有一個方法

三、實現原理不同:
查看項目文件夾中的時候可以看到

  • 匿名內部類:編譯之後會產生單獨的 .class 文件
  • Lambda 表達式:編譯之後不會有單獨的 .class 文件出現,對應的字節碼會在運行的時候動態生成。

轉載

https://blog.csdn.net/wanghao112956/article/details/91865095

https://www.cnblogs.com/dgwblog/p/11739500.html

https://cloud.tencent.com/developer/article/1333532

https://www.cnblogs.com/hellovan/p/13528201.html

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