java8新特性——函數式編程詳解

目錄

  • 一 概述
    • 1.1 背景
    • 1.2 函數式編程的意義
    • 1.3 函數式編程的發展
  • Lambda表達式
    • 1.1 介紹
    • 1.2 使用Lambda的好處
    • 1.3 Lambda方法
      • 1.3.1 Lambda表達式結構
      • 1.3.2 Lambda表達式的特征
    • 1.4 Lambda的使用
      • 1.4.1 定義函數式接口
      • 1.4.2 Lambda表達式實現函數式接口
      • 1.4.3 簡化Lambda表達式
      • 1.4.4 Lambda表達式引用方法
        • 1.4.4.1 類方法和成員方法的引用
        • 1.4.4.2 未綁定的方法引用
        • 1.4.4.3 構造函數引用
        • 1.4.4.4 總結
      • 1.4.5 Lambda創建線程
      • 1.4.6 Lambda 表達式中的閉包問題
  • 二 常用函數式接口
    • 2.1 基本類型
    • 2.2 非基本類型
    • 2.3 高階函數
    • 2.4 函數組合
    • 2.5 柯里化
  • 三 Lambda在stream流中的運用
    • 3.1 Stream流介紹
    • 3.2 Stream流的常用方法
      • 3.2.1 數據過濾
      • 3.2.2 數量限制
      • 3.2.3 元素排序
  • 四 總結

一 概述

1.1 背景

函數式編程的理論基礎是由阿隆佐·丘奇(Alonzo Church)于 1930 年提出的 λ 演算(Lambda Calculus),λ 演算是一種形式系統,用于研究函數定義、函數應用和遞歸的系統。它為計算理論和計算機科學的發展奠定了基礎。隨著 Haskell(1990年)和 Erlang(1986年)等新一代函數式編程語言的誕生,函數式編程開始在實際應用中開始發揮作用。

1.2 函數式編程的意義

隨著硬件越來越便宜,程序的規模和復雜性都在呈線性的增長。這一切都讓編程工作變得困難重重。我們想方設法使代碼更加一致和易懂。我們急需一種 語法優雅,簡潔健壯,高并發,易于測試和調試 的編程方式,這一切恰恰就是 函數式編程(FP) 的意義所在。

函數式編程語法讓代碼看起來更優雅,這些語法對于非函數式語言也適用。 例如 Python,Java 8 都在吸收 FP 的思想,并且將其融入其中,我們可以這樣認為:

OO(object oriented,面向對象)是抽象數據,而FP(functional programming,函數式編程)抽象是行為。

1.3 函數式編程的發展

定義接口Strategy.java

	interface Strategy {String approach(String msg);}
  1. 實現類方式
class StrategyImpl implements Strategy {public String approach(String msg) {return msg.toLowerCase();}Strategy strategy = new StrategyImpl();
}
  1. 匿名內部類方式
 Strategy strategy = new Strategy(){@Overridepublic String approach(String msg) {return msg.toUpperCase();}};
  1. Lambda表達式
Strategy strategy = m -> m.replace("abc","def");
  1. 方法引用
    定義一個與Strategy接口毫不相干的類
class Unrelated {//定義一個與approach簽名一樣的靜態方法static String twice(String msg) {return msg + " " + msg;}//再定義一個與approach簽名一樣的成員方法String approach(String msg){return msg+"$";}
}
public class Strategize {public static void main(String[] args) {//基于類方法引用,實例化 StrategyStrategy strategy = Unrelated::twice;//基于成員方法的引用,實例化 StrategyStrategy strategy2 = new Unrelated()::approach;}}

顯然Lambda 表達式使用更靈活,也更容易理解,因此官方推薦使用Lambda表達式。

Lambda表達式

1.1 介紹

在這里插入圖片描述

Lambda 表達式是 JDK8 的一個新特性,可以取代大部分的匿名內部類,寫出更優雅的 Java 代碼,尤其在集合的遍歷和其他集合操作中,可以極大地優化代碼結構。
在Java中,可以為變量賦予一個值:

int i = 130;
String str = "Hello World";
Boolean b = str.startWith("H");

能否把一個代碼塊賦給一個變更呢?

aBlockOfCode = public void doSomeShit(String s){System.out.println(s);}

在Java 8之前,這是做不到的。但是Java 8問世之后,利用Lambda特性,就可以做到了

甚至我們可以讓語法變得更簡潔
在這里插入圖片描述

aBlockOfCode = s -> System.out.println(s); //對,如你看到的優雅

在Java 8中,所有的Lambda的類型都是一個接口,而Lambda表達式本身,也就是”那段代碼“,就是這個接口的實現。這是我認為理解Lambda的一個關鍵所在,簡而言之就是,Lambda表達式本身就是一個接口的實現。直接這樣說可能還是有點讓人困擾,我們繼續看看例子。我們給上面的aBlockOfCode加上一個類型;

interface LambdaInterface{void doSomeShit(String s);
}LambdaInterface aBlockOfCode = s -> System.out.println(s);

像這種LambdaInterface只有一個抽象方法需要被實現的接口,我們叫它”函數式接口“。只有”函數式接口“才能使用Lambda表達式。為了避免后來的人在這個接口中增加接口函數導致其有多個抽象方法需要被實現,變成"非函數接口”,我們可以在這個接口上添加上一個聲明注解@FunctionalInterface, 這樣別人就無法在里面添加其它抽象方法了。

@FunctionalInterface
interface LambdaInterface{void doSomeShit(String s);
}

1.2 使用Lambda的好處

最直觀的好處就是使代碼變得異常簡潔。
在這里插入圖片描述
函數式接口規則
雖然使用 Lambda 表達式可以對某些接口進行簡單的實現,但并不是所有的接口都可以使用 Lambda 表達式來實現。Lambda 規定接口中只能有一個抽象方法,而不是規定接口中只能有一個方法。
jdk 8 中有另一個新特性:default, 被 default 修飾的方法會有默認實現,不是必須被實現的方法,所以不影響 Lambda 表達式的使用。也就是說一個接口中有且僅有一個抽象方法,如果有default修飾的方法,那么這個接口也函數式接口,同樣可以使用Lambda表達式。

@FunctionalInterface注解作用:
@FunctionalInterface標記在接口上,只允許標記的接口只能有一個抽象方法。

函數式接口:有且僅有一個抽象方法的接口。

1.3 Lambda方法

1.3.1 Lambda表達式結構

在這里插入圖片描述
語法結構:

(parameters)-> expression;

(parameters) -> {statements};

語法形式為 () -> {}:
() 用來描述參數列表,如果有多個參數,參數之間用逗號隔開,如果沒有參數,留空即可;
-> 讀作(goes to),為 lambda運算符 ,固定寫法,代表指向動作;
{} 代碼塊,具體要做的事情,也就是方法體內容。

1.3.2 Lambda表達式的特征

  • 可選類型聲明:不需要聲明參數類型,編譯器可以統一識別參數值類型;
  • 可選的參數圓括號”()“:一個參數無需定義圓括號,但零個或多個參數需要使用圓括號;
  • 可選的大括號”{ }“:如果主體包含了一個語句,就不需要使用大括號;
  • 可選的返回關鍵字:如果主體只有一個表達式返回值,則不需要寫關鍵字return,編譯器自動返回表達式的值,大括號需要使用return關鍵字指定具體的返回值。
// 1. 沒有參數需要使用圓括號,只有一條語句不需要使用大括號,也不用return指定返回值100
() -> 100 // 2. 接收一個參數(數字類型)不需要使用圓括號,只有一條語句不需要大括號也不用retrun指定返回其10倍的值 
x -> 10 * x // 3. 接受2個參數(數字),并返回他們的差值 
(x, y) -> x – y// 4. 接收2個int型整數,返回他們的和 
(x, y) -> x + y // 5. 接受一個 string 對象,并在控制臺打印,不返回任何值(看起來像是返回void) s -> System.out.print(s)

1.4 Lambda的使用

1.4.1 定義函數式接口

函數接口指的是在該接口中只能含有一個抽象方法。可以有多個default修飾的方法或者是static方法。


/*** @Author liqinglong* @DateTime 2024-05-15 10:09* @Version 1.0*/
@FunctionalInterface
public interface LambdaInterface {void doSomeShit(String s);
}//無返回值無參數的函數接口
@FunctionalInterface
interface NoReturnNoParam {public void method();
}//無返回值有一個參數的函數接口
@FunctionalInterface
interface NoReturnOneParam {public void method(int a);
}//無返回值有兩個參數的函數接口
@FunctionalInterface
interface NoReturnManyParam {public void method(int a, int b);
}//有返回值的無參數函數接口
@FunctionalInterface
interface ReturnNoParam {public int method();
}//有返回值有一個參數的函數接口
@FunctionalInterface
interface ReturnOneParam {public int method(int a);
}//有返回值有兩個參數的函數接口
@FunctionalInterface
interface ReturnManyParam {public int method(int a, int b);
}

1.4.2 Lambda表達式實現函數式接口

public class Test {public static void main(String[] args) {System.out.println("無返回值無參數的函數式接口的實現,類型就是接口名稱,{}表示對抽象方法的具體實現:");NoReturnNoParam noReturnNoParam = () -> {System.out.println("noReturnNoParam");};//調用該方法noReturnNoParam.method();System.out.println("無返回值一個參數的接口實現:");NoReturnOneParam noReturnOneParam = a -> {System.out.println("noReturnOneParam"+a);};//調用該方法noReturnOneParam.method(1);System.out.println("無返回值的兩個參數的接口實現:");NoReturnManyParam noReturnManyParam = (a,b) -> {System.out.println("noReturnManyParam"+a+","+b);};//調用該方法noReturnManyParam.method(1, 2);System.out.println("有返回值無參數的函數接口實現:");ReturnNoParam returnNoParam = () -> {System.out.println("returnNoParam");return 123;};//調用該方法int a = returnNoParam.method();System.out.println("a="+a);System.out.println("有返回值有一個參數的函數接口實現");ReturnOneParam returnOneParam = (p) -> {System.out.println("returnOneParam"+p);return p;};//調用該方法int b = returnOneParam.method(1);System.out.println("b="+b);System.out.println("有返回值有兩個參數的函數接口實現:");ReturnManyParam returnManyParam = (m, n) -> m + n;// 調用該方法int c = returnManyParam.method(1, 2);System.out.println("c="+c);}
}

運行結果:
在這里插入圖片描述

1.4.3 簡化Lambda表達式

  • 只有一個參數時小括號可以省略;
  • 參數列表中的參數類型可以寫,可以不寫。要寫都寫;
  • 當方法體之有一行代碼時,大括號可以省略;
  • 方法體中只有一行return語句時,return關鍵字可以省略。
//無返回值無參數的函數接口
@FunctionalInterface
interface NoReturnNoParam{public void method();
}//無返回值有一個參數的函數接口
@FunctionalInterface
interface NoReturnOneParam {public void method(int a);
}//無返回值有兩個參數的函數接口
@FunctionalInterface
interface NoReturnManyParam {public void method(int a, int b);
}//有返回值的無參數函數接口
@FunctionalInterface
interface ReturnNoParam {public int method();
}//有返回值有一個參數的函數接口
@FunctionalInterface
interface ReturnOneParam {public int method(int a);
}//有返回值有兩個參數的函數接口
@FunctionalInterface
interface ReturnManyParam {public int method(int a, int b);
}public class Test {public static void main(String[] args) {System.out.println("無返回值無參數的函數接口實現,方法體只有一行代碼時,{}可以不寫:");NoReturnNoParam noReturnNoParam = () -> System.out.println("noReturnNoParam");//調用該方法noReturnNoParam.method();System.out.println("無返回值一個參數的接口實現,當參數只有一個時,()可以不寫;方法體只有一行代碼時,{}可以不寫:");NoReturnOneParam noReturnOneParam = a-> System.out.println("noReturnOneParam"+a);//調用該方法noReturnOneParam.method(1);System.out.println("無返回值的兩個參數的接口實現,方法體只有一行代碼時,{}可以不寫:");NoReturnManyParam noReturnManyParam = (a, b) -> System.out.println("noReturnManyParam"+a+","+b);;//調用該方法noReturnManyParam.method(1, 2);System.out.println("有返回值無參數的函數接口實現:");ReturnNoParam returnNoParam = () -> {System.out.println("returnNoParam");return 123;};//調用該方法int a = returnNoParam.method();System.out.println("a="+a);System.out.println("有返回值有一個參數的函數接口實現,當只有一個參數時圓括號可以不寫:");ReturnOneParam returnOneParam = (p) -> {System.out.println("returnOneParam"+p);return p;};//調用該方法int b = returnOneParam.method(1);System.out.println("b="+b);System.out.println("當方法體只有return一行代碼時,return可以不寫:");ReturnManyParam returnManyParam = (m, n) -> m + n;// 調用該方法int c = returnManyParam.method(1, 2);System.out.println("c="+c);}
}

1.4.4 Lambda表達式引用方法

1.4.4.1 類方法和成員方法的引用

有時候我們不是必須使用Lambda的函數體定義實現,我們可以利用 lambda表達式指向一個已經存在的方法作為抽象方法的實現。
被引用的方法需滿足以下兩點

  • 參數個數以及類型需要與函數式接口中的抽象方法一致;
  • 返回值類型要與函數式接口中的抽象方法的返回值類型一致。

語法

方法歸屬者::方法名;
靜態方法的歸屬者為類名;
非靜態方法歸屬者為該對象的引用。

@FunctionalInterface
interface ReturnOne {public int method(int a);
}public class Test2 {//靜態方法public static int doubleNumber(int a){ return a*2; }//非靜態方法public int doubleNumber2(int a) {return a * 2;}public static void main(String[] args) {//將靜態方法作為接口中抽象方法的實現方法(前提是參數個數和參數類型必須相同)//Test2::doubleNumber表示抽象方法的實現方法是Test類下的doubleNumber方法ReturnOne returnOne1 = Test2::doubleNumber;//調用int method = returnOne1.method(10);System.out.println(method);//將非靜態方法作為接口中抽象方法的實現方法//先實例化非靜態方法的歸屬者,通過對象引用實現Test2 test2 = new Test2();ReturnOne returnOne2 = test2::doubleNumber2;int method2 = returnOne2.method(20);System.out.println(method2);}
}

運行結果:
在這里插入圖片描述

1.4.4.2 未綁定的方法引用

使用未綁定的引用時,需要先提供對象

// 未綁定的方法引用是指沒有關聯對象的普通方法
class X {String f() {return "X::f()";}
}interface MakeString {String make();
}interface TransformX {String transform(X x);
}public class UnboundMethodReference {public static void main(String[] args) {// MakeString sp = X::f;       // [1] 你不能在沒有 X 對象參數的前提下調用 f(),因為它是 X 的成員方法TransformX sp = X::f;       // [2] 你可以首個參數是 X 對象參數的前提下調用 f(),使用未綁定的引用,函數式的方法不再與方法引用的簽名完全相同X x = new X();System.out.println(sp.transform(x));      // [3] 傳入 x 對象,調用 x.f() 方法System.out.println(x.f());      // 同等效果}
}

運行結果:
在這里插入圖片描述
我們通過更多示例來證明,通過未綁的方法引用和 interface 之間建立關聯

// 未綁定的方法與多參數的結合運用
class This {void two(int i, double d) {System.out.println("two方法輸出:"+i +"," + d);}void three(int i, double d, String s) {System.out.println("three方法輸出:"+i +"," + d+","+s);}void four(int i, double d, String s, char c) {System.out.println("four方法輸出:"+i +"," + d+","+s+","+c);}
}
interface TwoArgs {void call2(This athis, int i, double d);
}
interface ThreeArgs {void call3(This athis, int i, double d, String s);
}
interface FourArgs {void call4(This athis, int i, double d, String s, char c);
}public class MultiUnbound {public static void main(String[] args) {TwoArgs twoargs = This::two;ThreeArgs threeargs = This::three;FourArgs fourargs = This::four;This athis = new This();twoargs.call2(athis, 11, 3.14);threeargs.call3(athis, 11, 3.14, "Three");fourargs.call4(athis, 11, 3.14, "Four", 'Z');}
}

運行結果:
在這里插入圖片描述

1.4.4.3 構造函數引用

可以捕獲構造函數的引用,然后通過引用構建對象

class Dog {String name;int age = -1; // For "unknown"Dog() { name = "stray"; }Dog(String name) { this.name = name; }Dog(String name, int age) {this.name = name;this.age = age;}void show(){System.out.println(name + "----------" + age);}
}interface MakeNoArgs {Dog make();
}interface Make1Arg {Dog make(String name);
}interface Make2Args {Dog make(String name, int age);
}public class CtorReference {public static void main(String[] args) {// 通過 ::new 關鍵字賦值給不同的接口,然后通過 make() 構建不同的實例MakeNoArgs mna = Dog::new; // [1] 將構造函數的引用交給 MakeNoArgs 接口Make1Arg m1a = Dog::new; // [2] …………Make2Args m2a = Dog::new; // [3] …………Dog dn = mna.make();dn.show();Dog d1 = m1a.make("Comet");d1.show();Dog d2 = m2a.make("Ralph", 4);d2.show();}
}

運行結果:
在這里插入圖片描述

1.4.4.4 總結
  • 方法引用在很大程度上可以理解為創建一個函數式接口的實例;

  • 方法引用實際上是一種簡化 Lambda 表達式的語法,它提供了一種更簡潔的方式來創建一個函數式接口的實現;

  • 在代碼中使用方法引用時,實際上是在創建一個匿名實現類,引用方法實現并且覆蓋了接口的抽象方法;

  • 方法引用大多用于創建函數式接口的實現。

1.4.5 Lambda創建線程

//Runnable接口中只有一個抽象方法run,也就是說Runnable是個函數式接口。
public class Test3 {public static void main(String[] args) {System.out.println("主線程"+ Thread.currentThread().getName()+"啟動!");//Lambda表達式實現run 方法。Runnable runnable = () -> {for (int i = 0; i < 10; i++ ) {System.out.println(Thread.currentThread().getName() + ", "+i);try {Thread.sleep(1000);} catch (InterruptedException e) {}}};//線程包裝Thread thread = new Thread(runnable, "Lambda線程");//線程啟動thread.start();System.out.println("主線程"+ Thread.currentThread().getName()+"結束!");}
}

或者直接將run方法的實現放在Thread構造方法中,這種寫法將更優雅,值得推薦

  new Thread(() -> {for (int i = 0; i < 10; i++ ) {System.out.println(Thread.currentThread().getName() + ", "+i);try {Thread.sleep(1000);} catch (InterruptedException e) {}}}, "Lambda線程").start();

運行結果:
在這里插入圖片描述

1.4.6 Lambda 表達式中的閉包問題

在 Java 中,閉包通常與 lambda 表達式和匿名內部類相關。簡單來說,閉包允許在一個函數內部訪問和操作其外部作用域中的變量。在 Java 中的閉包實際上是一個特殊的對象,它封裝了一個函數及其相關的環境。這意味著閉包不僅僅是一個函數,它還攜帶了一個執行上下文,其中包括外部作用域中的變量。這使得閉包在訪問這些變量時可以在不同的執行上下文中保持它們的值。

讓我們通過一個例子來理解 Java 中的閉包:

import java.util.function.IntBinaryOperator;public class ClosureExample {public static void main(String[] args) {int a = 10;int b = 20;// 這是一個閉包,因為它捕獲了外部作用域中的變量 a 和 bIntBinaryOperator closure = (x, y) -> x * a + y * b;int result = closure.applyAsInt(3, 4);System.out.println("Result: " + result); // 輸出 "Result: 110"}
}

需要注意的是,在 Java 中,閉包捕獲的外部變量必須是 final 或者是有效的 final(即在實際使用過程中保持不變)。這是為了防止在多線程環境中引起不可預測的行為和數據不一致。

二 常用函數式接口

java.util.function 包旨在創建一組完整的預定義接口,使得我們一般情況下不需再定義自己的接口。

java.util.function 包中的的函數式接口的使用基本準測

  • 只處理對象而非基本類型,名稱則為 Function,Consumer,Predicate 等,參數通過泛型添加;
  • 如果接收的參數是基本類型,則由名稱的第一部分表示,如 LongConsumer, DoubleFunction,IntPredicate 等;
  • 如果返回值為基本類型,則用 To 表示,如 ToLongFunction 和 IntToLongFunction;
  • 如果返回值類型與參數類型一致,則是一個運算符;
  • 如果接收兩個參數且返回值為布爾值,則是一個謂詞(Predicate);
  • 如果接收的兩個參數類型不同,則名稱以 Bi開頭。

2.1 基本類型

基本類型相關的函數式接口:
在這里插入圖片描述
下面枚舉了基于 Lambda 表達式的所有不同 Function 變體的示例

import java.util.function.*;class Foo {}
class Bar {Foo f;Bar(Foo f) { this.f = f; }
}class IBaz {int i;IBaz(int i) { this.i = i; }
}class LBaz {long l;LBaz(long l) { this.l = l; }
}class DBaz {double d;DBaz(double d) { this.d = d; }
}public class FunctionVariants {// 根據不同參數獲得對象的函數表達式static Function<Foo, Bar> f1 = f -> new Bar(f);static IntFunction<IBaz> f2 = i -> new IBaz(i);static LongFunction<LBaz> f3 = l -> new LBaz(l);static DoubleFunction<DBaz> f4 = d -> new DBaz(d);// 根據對象類型參數,獲得基本數據類型返回值的函數表達式static ToIntFunction<IBaz> f5 = ib -> ib.i;static ToLongFunction<LBaz> f6 = lb -> lb.l;static ToDoubleFunction<DBaz> f7 = db -> db.d;static IntToLongFunction f8 = i -> i;static IntToDoubleFunction f9 = i -> i;static LongToIntFunction f10 = l -> (int)l;static LongToDoubleFunction f11 = l -> l;static DoubleToIntFunction f12 = d -> (int)d;static DoubleToLongFunction f13 = d -> (long)d;public static void main(String[] args) {// apply usage examplesBar b = f1.apply(new Foo());IBaz ib = f2.apply(11);LBaz lb = f3.apply(11);DBaz db = f4.apply(11);// applyAs* usage examplesint i = f5.applyAsInt(ib);long l = f6.applyAsLong(lb);double d = f7.applyAsDouble(db);// 基本類型的相互轉換long applyAsLong = f8.applyAsLong(12);double applyAsDouble = f9.applyAsDouble(12);int applyAsInt = f10.applyAsInt(12);double applyAsDouble1 = f11.applyAsDouble(12);int applyAsInt1 = f12.applyAsInt(13.0);long applyAsLong1 = f13.applyAsLong(13.0);}
}

2.2 非基本類型

非基本類型的函數式接口
在這里插入圖片描述
在使用函數接式口時,名稱無關緊要——只要參數類型和返回類型相同。Java 會將你的方法映射到接口方法:

import java.util.function.BiConsumer;class In1 {}
class In2 {}public class MethodConversion {static void accept(In1 in1, In2 in2) {System.out.println("accept()");}static void someOtherName(In1 in1, In2 in2) {System.out.println("someOtherName()");}static void other(In1 in1, In2 in2) {System.out.println("other()");}public static void main(String[] args) {BiConsumer<In1, In2> bic;bic = MethodConversion::accept;bic.accept(new In1(), new In2());// 在使用函數接口時,名稱無關緊要——只要參數類型和返回類型相同。Java 會將你的方法映射到接口方法。bic = MethodConversion::someOtherName;bic.accept(new In1(), new In2());bic = MethodConversion::other;bic.accept(new In1(),new In2());}
}

運行結果:
在這里插入圖片描述

將方法引用應用于基于類的函數式接口(即那些不包含基本類型的函數式接口)

import java.util.Comparator;
import java.util.function.*;class AA {}
class BB {}
class CC {}public class ClassFunctionals {static AA f1() { return new AA(); }static int f2(AA aa1, AA aa2) { return 1; }static void f3 (AA aa) {}static void f4 (AA aa, BB bb) {}static CC f5 (AA aa) { return new CC(); }static CC f6 (AA aa, BB bb) { return new CC(); }static boolean f7 (AA aa) { return true; }static boolean f8 (AA aa, BB bb) { return true; }static AA f9 (AA aa) { return new AA(); }static AA f10 (AA aa, AA bb) { return new AA(); }public static void main(String[] args) {// 無參數,返回一個結果Supplier<AA> s = ClassFunctionals::f1;s.get();// 比較兩個對象,用于排序和比較操作Comparator<AA> c = ClassFunctionals::f2;c.compare(new AA(), new AA());// 執行操作,通常是副作用操作,不需要返回結果Consumer<AA> cons = ClassFunctionals::f3;cons.accept(new AA());// 執行操作,通常是副作用操作,不需要返回結果,接受兩個參數BiConsumer<AA, BB> bicons = ClassFunctionals::f4;bicons.accept(new AA(), new BB());// 將輸入參數轉換成輸出結果,如數據轉換或映射操作Function<AA, CC> f = ClassFunctionals::f5;CC cc = f.apply(new AA());// 將兩個輸入參數轉換成輸出結果,如數據轉換或映射操作BiFunction<AA, BB, CC> bif = ClassFunctionals::f6;cc = bif.apply(new AA(), new BB());// 接受一個參數,返回 boolean 值: 測試參數是否滿足特定條件Predicate<AA> p = ClassFunctionals::f7;boolean result = p.test(new AA());// 接受兩個參數,返回 boolean 值,測試兩個參數是否滿足特定條件BiPredicate<AA, BB> bip = ClassFunctionals::f8;result = bip.test(new AA(), new BB());// 接受一個參數,返回一個相同類型的結果,對輸入執行單一操作并返回相同類型的結果,是 Function 的特殊情況UnaryOperator<AA> uo = ClassFunctionals::f9;AA aa = uo.apply(new AA());// 接受兩個相同類型的參數,返回一個相同類型的結果,將兩個相同類型的值組合成一個新值,是 BiFunction 的特殊情況BinaryOperator<AA> bo = ClassFunctionals::f10;aa = bo.apply(new AA(), new AA());}
}

多參數函數式接口java.util.functional 中的接口是有限的,若需要 3 個參數函數的接口,我們可以自己定義:

// 創建處理 3 個參數的函數式接口
@FunctionalInterface
public interface TriFunction<T, U, V, R> {R apply(T t, U u, V v);
}

使用

public class TriFunctionTest {static int f(int i, long l, double d) {return (int) (i+l+d);}public static void main(String[] args) {// 方法引用TriFunction<Integer, Long, Double, Integer> tf1 = TriFunctionTest::f;System.out.println(tf1.apply(1,2L,3.0));// Lamdba 表達式TriFunction<Integer, Long, Double, Integer> tf2 = (i, l, d) -> (int)(i+l+d);System.out.println(tf2.apply(1,2L,3.0));}
}

運行結果:
在這里插入圖片描述

2.3 高階函數

高階階函數(Higher-order Function)其實很好理解,并且在函數式編程中非常常見,它有以下特點:

  • 接收一個或多個函數作為參數
  • 返回一個函數作為結果

先來看看一個函數如何返回一個函數

import java.util.function.Function;//使用繼承,輕松創建屬于自己的函數式接口
interface FuncSS extends Function<String, String> {} public class ProduceFunction {// produce() 是一個高階函數:即函數的消費者,產生函數的函數static FuncSS produce() {return s -> s.toLowerCase();    //使用 Lambda 表達式,可以輕松地在方法中創建和返回一個函數}public static void main(String[] args) {FuncSS funcSS = produce();System.out.println(funcSS.apply("YELLOW"));}
}

運行結果:
在這里插入圖片描述
然后再看看,如何接收一個函數作為函數的參數

import java.util.function.Function;class One {}
class Two {public void out(){System.out.println("Two的out方法輸出的");}
}public class ConsumeFunction {static Two consume(Function<One, Two> onetwo) {return onetwo.apply(new One());}public static void main(String[] args) {Two two = consume(one -> new Two());two.out();}
}

運行結果:
在這里插入圖片描述
總之,高階函數使代碼更加簡潔、靈活和可重用,常見于 Stream 流式編程中。

2.4 函數組合

函數組合(Function Composition)意為 “多個函數組合成新函數”。它通常是函數式 編程的基本組成部分。

先看 Function 函數組合示例代碼:

import java.util.function.Function;public class FunctionComposition {static Function<String, String> f1 = s -> {System.out.println(s);return s.replace('A', '_');},f2 = s -> s.substring(3),f3 = s -> s.toLowerCase(),// 重點:使用函數組合將多個函數組合在一起// compose 是先執行參數中的函數,再執行調用者// andThen 是先執行調用者,再執行參數中的函數f4 = f1.compose(f2).andThen(f3);public static void main(String[] args) {String s = f4.apply("GO AFTER ALL AMBULANCES");System.out.println(s);}
}

代碼示例使用了 Function 里的 compose() 和 andThen(),它們的區別如下:

  • compose 是先執行參數中的函數,再執行調用者;
  • andThen 是先執行調用者,再執行參數中的函數。

運行結果:
在這里插入圖片描述
然后,再看一段 Predicate 的邏輯運算演示代碼

import java.util.function.Predicate;
import java.util.stream.Stream;public class PredicateComposition {static Predicate<String>p1 = s -> s.contains("bar"),p2 = s -> s.length() < 5,p3 = s -> s.contains("foo"),p4 = p1.negate().and(p2).or(p3);    // 使用謂詞組合將多個謂詞組合在一起,negate 是取反,and 是與,or 是或public static void main(String[] args) {Stream.of("bar", "foobar", "foobaz", "fongopuckey").filter(p4).forEach(System.out::println);}
}

p4 通過函數組合生成一個復雜的謂詞,最后應用在 filter() 中:

  • negate():取反值,內容不包含 bar
  • and(p2):長度小于 5
  • or(p3):或者包含 f3
    運行結果:
    在這里插入圖片描述
    在 java.util.function 中常用的支持函數組合的方法,大致如下:
    在這里插入圖片描述

2.5 柯里化

柯里化(Currying)是函數式編程中的一種技術,它將一個接受多個參數的函數轉換為一系列單參數函數。
讓我們通過一個簡單的 Java 示例來理解柯里化:

import java.util.function.Function;public class CurryingAndPartials {static String uncurried(String a, String b) {return a + b;}public static void main(String[] args) {// 柯里化的函數,它是一個接受多參數的函數Function<String, Function<String, String>> sum = a -> b -> a + b;// 通過鏈式調用逐個傳遞參數Function<String, String> hi = sum.apply("Hi ");System.out.println(hi.apply("Ho"));System.out.println(hi.apply("Hey"));Function<String, String> sumHi = sum.apply("Hup ");System.out.println(sumHi.apply("Ho"));System.out.println(sumHi.apply("Hey"));}
}

運行結果:
在這里插入圖片描述
接下來我們添加層級來柯里化一個三參數函數:

import java.util.function.Function;public class Curry3Args {public static void main(String[] args) {// 柯里化函數Function<String,Function<String,Function<String, String>>> sum = a -> b -> c -> a + b + c;// 逐個傳遞參數Function<String, Function<String, String>> hi = sum.apply("One ");Function<String, String> ho = hi.apply("Two ");System.out.println(ho.apply("Three"));}
}

運行結果:
在這里插入圖片描述
在處理基本類型的時候,注意選擇合適的函數式接口:

import java.util.function.IntFunction;
import java.util.function.IntUnaryOperator;public class CurriedIntAdd {public static void main(String[] args) {IntFunction<IntUnaryOperator> curriedIntAdd = a -> b -> a + b;IntUnaryOperator add4 = curriedIntAdd.apply(4);System.out.println(add4.applyAsInt(5));}
}

運行結果:
在這里插入圖片描述

三 Lambda在stream流中的運用

3.1 Stream流介紹

在這里插入圖片描述
Stream是數據渠道,用于操作數據源所生成的元素序列,它可以實現對集合的復雜操作,例如過濾、排序和映射等。Stream不會改變源對象,而是返回一個新的結果集

Stream流的生成方式
生成流:通過數據源(集合、數組等)創建一個流。
中間操作:一個流后面可以跟隨零個或者多個中間操作,其目的主要是打開流,做出某種程度的數據過濾/映射,然后返回一個新的流,交給下一個操作使用。
終結操作:一旦執行終止操作,就執行中間的鏈式操作,并產生結果。
collection接口中有一個Stream類型的方法,該接口下的實現類實現了該抽象方法,也就是說Collection接口下的List、set等單例集合都存在一個Stream方法返回一個對應的Streatm對象。
在這里插入圖片描述

3.2 Stream流的常用方法

3.2.1 數據過濾

在這里插入圖片描述

 
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;public class TestStream {public static void main(String[] args) {//生成Stream流對象List<String> list = new ArrayList<>();list.add("a");list.add("王五");list.add("張三");list.add("李四");list.add("張大大");list.add("張紹剛");Stream<String> stream = list.stream();//操作流對象//數據過濾,實現多條件and關系,以張開頭三結尾的元素List<String> collect = stream.filter(o -> o.startsWith("張")).filter(o -> o.endsWith("三")).collect(Collectors.toList());//遍歷過濾后的結合collect.forEach(System.out::println);System.out.println("--------------------");Stream<String> stream1 = list.stream();//多條件的or關系//先創建or關系Predicate<String> predicate = (o) ->o.startsWith("張");Predicate<String> predicate2 = (o) -> o.startsWith("李");List<String> collect1 = stream1.filter(predicate.or(predicate2)).collect(Collectors.toList());//遍歷過濾后的結合collect1.forEach(System.out::println);}
}

運行結果:
在這里插入圖片描述

3.2.2 數量限制

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;public class TestStream2 {public static void main(String[] args) {//生成Stream流對象List<String> list = new ArrayList<>();list.add("a");list.add("王五");list.add("張三");list.add("李四");list.add("張紹剛");Stream<String> stream = list.stream();//獲取前兩個元素List<String> collect = stream.limit(2).collect(Collectors.toList());//遍歷collect.forEach(System.out:: println);}
}

運行結果:
在這里插入圖片描述

3.2.3 元素排序

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;public class TestStream3 {public static void main(String[] args) {List<String> list = new ArrayList<>();list.add("a");list.add("王五");list.add("張三");list.add("李四");list.add("張紹剛");//按照升序排序List<String> collect = list.stream().sorted().collect(Collectors.toList());//遍歷collect.forEach(System.out::println);System.out.println("--------------------");//降序list.stream().sorted(Comparator.reverseOrder()).forEach(System.out::println);}
}

運行結果:
在這里插入圖片描述

四 總結

Lambda 表達式和方法引用并沒有將 Java 轉換成函數式語言,而是提供了對函數式編程的支持(Java 的歷史包袱太重了),這些特性滿足了很大一部分的、羨慕 Clojure 和 Scala 這類更函數化語言的 Java 程序員。阻止了他們投奔向那些語言(或者至少讓他們在投奔之前做好準備)。總之,Lambdas 和方法引用是 Java 8 中的巨大改進。

學習本篇后相信大家對函數式編程有清晰的認識,知道函數式接口指的是只含有一個抽象方法的接口,明白Lambda表達式就是函數式接口的實現,理解函數式編程中如何引用方法等

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/15618.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/15618.shtml
英文地址,請注明出處:http://en.pswp.cn/web/15618.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

C++學習/復習4--與類相關的概念/默認成員函數/運算符重載/Date類實現案例

一、類和對象 1.本章概要 2.C中的結構體(struct與class) 升級為類 &#xff08;1&#xff09;類及成員函數的兩種定義方式 聲明與定義分離 &#xff08;2&#xff09;權限 注意1&#xff1a;struct/class在權限上的區別 &#xff08;3&#xff09;封裝 &#xff08;4&#x…

AI學習指南數學工具篇-凸優化之對偶性與拉格朗日對偶

AI學習指南數學工具篇-凸優化之對偶性與拉格朗日對偶 在凸優化中&#xff0c;對偶性是一個非常重要的概念。通過對偶性&#xff0c;我們可以將原始問題轉化為對偶問題&#xff0c;從而更容易求解。其中&#xff0c;拉格朗日對偶問題是對偶性的一個重要應用&#xff0c;通過拉格…

《Ai學習筆記》自然語言處理 (Natural Language Processing):機器閱讀理解-基礎概念解析01

自然語言處理 (Natural Language Processing)&#xff1a; NLP四大基本任務 序列標注&#xff1a; 分詞、詞性標注 分類任務&#xff1a; 文本分類、情感分析 句子關系&#xff1a;問答系統、對話系統 生成任務&#xff1a;機器翻譯、文章摘要 機器閱讀理解的定義 Machi…

LangChain - 建立代理

本文翻譯整理自&#xff1a;Build an Agent https://python.langchain.com/v0.2/docs/tutorials/agents/ 文章目錄 一、說明概念 二、定義工具1、TavilyAPI參考&#xff1a; 2、RetrieverAPI參考&#xff1a;API參考&#xff1a; 3、工具 三、使用語言模型四、創建代理五、運行…

《安富萊嵌入式周報》第337期:超高性能信號量測量,協議分析的開源工具且核心算法開源,工業安全應用的雙通道數字I/O模組,低成本腦機接口,開源音頻合成器

周報匯總地址&#xff1a;http://www.armbbs.cn/forum.php?modforumdisplay&fid12&filtertypeid&typeid104 視頻版&#xff1a; https://link.zhihu.com/?targethttps%3A//www.bilibili.com/video/BV1PT421S7TR/ 《安富萊嵌入式周報》第337期&#xff1a;超高性…

【Spring Boot】分層開發 Web 應用程序(含實例)

分層開發 Web 應用程序 1.應用程序分層開發模式&#xff1a;MVC1.1 了解 MVC 模式1.2 MVC 和三層架構的關系 2.視圖技術 Thymeleaf3.使用控制器3.1 常用注解3.1.1 Controller3.1.2 RestController3.1.3 RequestMapping3.1.4 PathVariable 3.2 將 URL 映射到方法3.3 在方法中使用…

用戶數據報協議UDP實現可靠傳輸的思路

一、UDP協議的特點 按照報文來分割發送。不需要建立連接和維護連接。不需要接收確認。速度較快。不確保接收的順序和發送順序一樣。 二、用UDP實現可靠通信的思路 (一)接收時發送一個確認報文 實現接收確認的機制。 (二)每個報文騰出空間放置序號 發送時設置序號&#xff0c…

如何安裝虛擬機Wmware,并且在虛擬機中使用centos系統

1. 前言 大家好&#xff0c;我是jiaoxingk 本篇文章主要講解如何安裝虛擬機&#xff0c;并且在虛擬機中安裝centos系統&#xff0c;讓windows電腦也能夠使用Linux系統 2. 虛擬機的介紹 在安裝Vmware之前&#xff0c;我們先做虛擬機的介紹 虛擬機&#xff1a;通過軟件虛擬出來的…

Docker拉取鏡像報錯:x509: certificate has expired or is not yet v..

太久沒有使用docker進行鏡像拉取&#xff0c;今天使用docker-compose拉取mongo發現報錯&#xff08;如下圖&#xff09;&#xff1a; 報錯信息翻譯&#xff1a;證書已過期或尚未有效。 解決辦法&#xff1a; 1.一般都是證書問題或者系統時間問題導致&#xff0c;可以先執行 da…

用HAL庫改寫江科大的stm32入門例子-6-2 定時器外部時鐘

實驗目的&#xff1a; 熟悉外部時鐘的應用。 實驗步驟&#xff1a; 創建項目參照前面的文章&#xff0c;集成oled(沒有oled,用uart串口傳遞也可以)選擇外部時鐘源時鐘源參數設置編寫代碼&#xff1a; 5.1聲明全局變量&#xff0c;如果發生定時器中斷的時候&#xff0c;在回調…

SW 零件插入零件的重合配合

重合配合有時候會失效,可以先用距離配合代替,之后修改距離盡量接近

AI網絡爬蟲-自動獲取百度實時熱搜榜

工作任務和目標&#xff1a;自動獲取百度實時熱搜榜的標題和熱搜指數 標題&#xff1a;<div class"c-single-text-ellipsis"> 東部戰區臺島戰巡演練模擬動畫 <!--48--></div> <div class"hot-index_1Bl1a"> 4946724 </div> …

【bash】統計服務器信息腳本

起因 寫一個bash腳本統計服務器的機器名、內網IP、CPU使用率、內存使用率、List{GPU使用率、顯存} 腳本 #!/bin/bash# 主機名 hostname$(hostname) # 內網ip ip$(ip addr | grep inet | grep -v 127.0.0.1 | awk {print $2} | cut -d/ -f1) ip$(echo "$ip"|tr \n…

Excel表格在線解密:輕松解密密碼,快速恢復數據

忘記了excel表格密碼&#xff1f;教你簡單兩步走&#xff1a;具體步驟如下。首先&#xff0c;在百度搜索中鍵入“密碼帝官網”。其次&#xff0c;點擊“立即開始”&#xff0c;在用戶中心上傳表格文件即可找回密碼。這種方法不用下載軟件&#xff0c;操作簡單易行&#xff0c;適…

【DZ模板】價值288克米設計APP手機版DZ模板 數據本地化+完美使用

模版介紹 【DZ模板】價值288克米設計APP手機版DZ模板 數據本地化完美使用 騰訊官方出品discuz論壇DIY的后臺設置&#xff0c;功能齊全&#xff0c;論壇功能不亞于葫蘆俠&#xff0c;自定義馬甲&#xff0c;自定義認證&#xff0c;自定義廣告&#xff0c;完全可以打造出自己想…

元本學堂是什么?杜旭東疑似再翻車!

杜旭東&#xff0c;1956年1月7日出生于中國北京市&#xff0c;畢業于解放軍藝術學院&#xff0c;中國內地男演員、國家一級演員&#xff01; 2023年11月17日晚&#xff0c;杜旭東在其個人社交媒體上發布視頻&#xff0c;就其以前給緬北電詐集團的白家成員錄制慶生視頻一事道歉…

C++11std::bind的簡單使用

std::bind用來將可調用對象與其參數一起進行綁定&#xff0c;綁定后的結果可以用std::function&#xff08;可調用對象包裝器&#xff09;進行保存&#xff0c;并延遲調用到任何我們需要的時候。 通俗來講&#xff0c;它主要有兩大作用&#xff1a; &#xff08;1&#xff09…

每日一題Cat, Fox and the Lonely Array

文章目錄 題名&#xff1a;題意&#xff1a;題解&#xff1a;代碼&#xff1a; 題名&#xff1a; Cat, Fox and the Lonely Array 題意&#xff1a; 給定一個數組a&#xff0c;求出最小的k&#xff0c;滿足數組每個長度為k的連續子數組元素按位或答案都相等。 題解&#xf…

【AI新時代】擁抱未來,用AI無人直播替代真人直播,解放勞動力,控制成本!

在科技日新月異的新時代&#xff0c;人工智能&#xff08;AI&#xff09;的 keJ0277 浪潮正在席卷各行各業&#xff0c;為傳統的工作模式帶來了前所未有的變革。其中&#xff0c;AI無人直播的興起&#xff0c;無疑是這場科技革命中的一股強勁力量。它以其獨特的優勢&#xff0…