当前位置: 首页 > news >正文

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表达式就是函数式接口的实现,理解函数式编程中如何引用方法等

相关文章:

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.…...

mybatis-plus小课堂: apply 拼接 in SQL,来查询从表某个范围内的数据

文章目录 引言I mybatis-Plus 之 apply 拼接 in SQL1.1 apply源码实现1.2 apply 拼接 in SQL : 非字符串数组1.3 apply 拼接 in SQL : 字符串数组II 如果in的数量太多,采用子查询。III 常见问题: Cause: comColumn xxx in where clause is ambiguoussee also引言 I mybati…...

民宿推荐系统-手把手调试搭建

民宿推荐系统-手把手调试搭建 民宿推荐系统-手把手调试搭建...

线性回归模型

目录 1.概述 2.线性回归模型的定义 3.线性回归模型的优缺点 4.线性回归模型的应用场景 5.线性回归模型的未来展望 6.小结 1.概述 线性回归是一种广泛应用于统计学和机器学习的技术&#xff0c;用于研究两个或多个变量之间的线性关系。在本文中&#xff0c;我们将深入探讨…...

西门子全球业务调整:数十亿欧元交易额,开启新篇章

导语 大家好&#xff0c;我是社长&#xff0c;老K。专注分享智能制造和智能仓储物流等内容。 新书《智能物流系统构成与技术实践》 导语 大家好&#xff0c;我是社长&#xff0c;老K。专注分享智能制造和智能仓储物流等内容。 在风起云涌的全球经济舞台上&#xff0c;西门子&am…...

AI遇上遥感,未来会怎样?

随着航空、航天、近地空间等多个遥感平台的不断发展&#xff0c;近年来遥感技术突飞猛进。由此&#xff0c;遥感数据的空间、时间、光谱分辨率不断提高&#xff0c;数据量也大幅增长&#xff0c;使其越来越具有大数据特征。对于相关研究而言&#xff0c;遥感大数据的出现为其提…...

认知架构 cognitive architecture

Assistants API&#xff1a;以开发人员为中心。 有状态的API&#xff1a;允许存储以前的消息、上传文件、访问内置工具&#xff08;代码解释器&#xff09;、通过函数调用控制其他工具。 认知架构应用的两个组件&#xff1a;&#xff08;1&#xff09;如何提供上下文给应用 &…...

数据插值之朗格朗日插值(一)

目录 一、引言 二、代码实现 2.1 Lagrange插值求插值多项式&#xff1a; 代码解析&#xff1a; 1.vpa解释 2.ploy&#xff08;x&#xff09;解释: 3.conv&#xff08;&#xff09;解释 4.poly2sym()解释 2.2 Lagrange插值求新样本值和误差估计&#xff1a; 代码解析&…...

【CCF-CSP】 202309-3 梯度求解

思路&#xff1a; 将表达式整理成只有目标求导变量的无括号加法表达式&#xff0c;其他变量均代入其值&#xff0c;然后利用最简单的求导公式&#xff0c;求出最终值。 样例1 x1 x1 x1 * x2 *转换成 x1*x1*x1x1*x2 若求导x1&#xff0c;则只留下x1&#xff0c;变为 x1*x1*x1…...

jvm的类加载

文章目录 概要加载类加载器分类双亲委派模型自定义加载器 验证准备解析初始化<cinit>与<init> 概要 jvm运行时的整体结构如下 一个Car类&#xff0c;类跟Car对象的转换过程如下&#xff1a; 加载后的class类信息存放于方法区&#xff1b;ClassLoader只负责clas…...

2024年汉字小达人活动4个多月开赛:18道历年选择题和答案、解析

根据近年的安排&#xff0c;2024年第11届汉字小达人比赛还有4个多月就启动&#xff0c;那么孩子们如何利用这段时间有条不紊地备考呢&#xff1f;我的建议是两手准备&#xff1a;①把小学1-5年级的语文课本上的知识点熟悉&#xff0c;重点是字、词、成语、古诗。②把历年真题刷…...

群晖安装青龙脚本

青龙定时任务管理面板&#xff0c;支持 Python3、JavaScript、Shell、Typescript 这几种环境&#xff0c;通过它可以方便的管理和运行定时任务&#xff08;在某个时间执行一段代码&#xff09;&#xff0c;并且只需简单的配置&#xff0c;就可以在各个平台收到任务执行的结果通…...

【机器学习系列】使用高斯贝叶斯模型进行数据分类的完整流程

目录 一、导入数据 二、选择特征 三、十折交叉验证 四、划分训练集和测试集 五、训练高斯贝叶斯模型 六、预测测试集 七、查看训练集和测试集上的分数 八、查看混合矩阵 九、输出评估指标 一、导入数据 # 根据商户数据预测其是否续约案例 import pandas #读取数据到 da…...

Python中的单例模式:原理、实现与应用

Python中的单例模式&#xff1a;原理、实现与应用 一、引言 在软件开发中&#xff0c;设计模式是一种用于解决常见问题的最佳实践。单例模式&#xff08;Singleton Pattern&#xff09;是这些设计模式中的一种&#xff0c;它确保一个类仅有一个实例&#xff0c;并提供一个全局…...

Linux基础(六):Linux 系统上 C 程序的编译与调试

本篇博客详细分析&#xff0c;Linux平台上C程序的编译过程与调试方法&#xff0c;这也是我们后续程序开发的基础。 目录 一、第一个hello world程序 1.1 创建.c文件 1.2 编译链接 运行可执行程序 二、编译链接过程 2.1 预编译阶段 2.2 编译阶段 2.3 汇编阶段 2.4 链…...

移动硬盘难题:不显示容量与无法访问的解决策略

在使用移动硬盘的过程中&#xff0c;有时会遇到一些棘手的问题&#xff0c;比如移动硬盘不显示容量且无法访问。这种情况让人十分头疼&#xff0c;因为它不仅影响了数据的正常使用&#xff0c;还可能导致重要数据的丢失。接下来&#xff0c;我们就来详细探讨一下这个问题及其解…...

基于springboot+vue的智慧外贸平台

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…...

@Async详解,为什么生产环境不推荐直接使用@Async?

一、Async 注解介绍&#xff1a; Async 注解用于声明一个方法是异步的。当在方法上加上这个注解时&#xff0c;Spring 将会在一个新的线程中执行该方法&#xff0c;而不会阻塞原始线程。这对于需要进行一些异步操作的场景非常有用&#xff0c;比如在后台执行一些耗时的任务而不…...

LaTeX 2022软件安装教程(附软件下载地址)

软件简介&#xff1a; 软件【下载地址】获取方式见文末。注&#xff1a;推荐使用&#xff0c;更贴合此安装方法&#xff01; LaTeX 2022是基于ΤΕΧ的一种排版系统&#xff0c;特别适用于生成科技和数学文档的高质量打印。它可用于各种文档类型&#xff0c;从简单信函到完整…...

纯干货分享 机器学习7大方面,30个硬核数据集

在刚刚开始学习算法的时候&#xff0c;大家有没有过这种感觉&#xff0c;最最重要的那必须是算法本身&#xff01; 其实在一定程度上忽略了数据的重要性。 而事实上一定是&#xff0c;质量高的数据集可能是最重要的&#xff01; 数据集在机器学习算法项目中具有非常关键的重…...

Python爬虫实战:研究MechanicalSoup库相关技术

一、MechanicalSoup 库概述 1.1 库简介 MechanicalSoup 是一个 Python 库,专为自动化交互网站而设计。它结合了 requests 的 HTTP 请求能力和 BeautifulSoup 的 HTML 解析能力,提供了直观的 API,让我们可以像人类用户一样浏览网页、填写表单和提交请求。 1.2 主要功能特点…...

JavaScript 中的 ES|QL:利用 Apache Arrow 工具

作者&#xff1a;来自 Elastic Jeffrey Rengifo 学习如何将 ES|QL 与 JavaScript 的 Apache Arrow 客户端工具一起使用。 想获得 Elastic 认证吗&#xff1f;了解下一期 Elasticsearch Engineer 培训的时间吧&#xff01; Elasticsearch 拥有众多新功能&#xff0c;助你为自己…...

在Ubuntu中设置开机自动运行(sudo)指令的指南

在Ubuntu系统中&#xff0c;有时需要在系统启动时自动执行某些命令&#xff0c;特别是需要 sudo权限的指令。为了实现这一功能&#xff0c;可以使用多种方法&#xff0c;包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法&#xff0c;并提供…...

【配置 YOLOX 用于按目录分类的图片数据集】

现在的图标点选越来越多&#xff0c;如何一步解决&#xff0c;采用 YOLOX 目标检测模式则可以轻松解决 要在 YOLOX 中使用按目录分类的图片数据集&#xff08;每个目录代表一个类别&#xff0c;目录下是该类别的所有图片&#xff09;&#xff0c;你需要进行以下配置步骤&#x…...

大学生职业发展与就业创业指导教学评价

这里是引用 作为软工2203/2204班的学生&#xff0c;我们非常感谢您在《大学生职业发展与就业创业指导》课程中的悉心教导。这门课程对我们即将面临实习和就业的工科学生来说至关重要&#xff0c;而您认真负责的教学态度&#xff0c;让课程的每一部分都充满了实用价值。 尤其让我…...

项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)

Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败&#xff0c;具体原因是客户端发送了密码认证请求&#xff0c;但Redis服务器未设置密码 1.为Redis设置密码&#xff08;匹配客户端配置&#xff09; 步骤&#xff1a; 1&#xff09;.修…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

中医有效性探讨

文章目录 西医是如何发展到以生物化学为药理基础的现代医学&#xff1f;传统医学奠基期&#xff08;远古 - 17 世纪&#xff09;近代医学转型期&#xff08;17 世纪 - 19 世纪末&#xff09;​现代医学成熟期&#xff08;20世纪至今&#xff09; 中医的源远流长和一脉相承远古至…...

C++:多态机制详解

目录 一. 多态的概念 1.静态多态&#xff08;编译时多态&#xff09; 二.动态多态的定义及实现 1.多态的构成条件 2.虚函数 3.虚函数的重写/覆盖 4.虚函数重写的一些其他问题 1&#xff09;.协变 2&#xff09;.析构函数的重写 5.override 和 final关键字 1&#…...

AI+无人机如何守护濒危物种?YOLOv8实现95%精准识别

【导读】 野生动物监测在理解和保护生态系统中发挥着至关重要的作用。然而&#xff0c;传统的野生动物观察方法往往耗时耗力、成本高昂且范围有限。无人机的出现为野生动物监测提供了有前景的替代方案&#xff0c;能够实现大范围覆盖并远程采集数据。尽管具备这些优势&#xf…...