java8新特性——函数式编程详解

news2024/9/22 23:33:15

目录

  • 一 概述
    • 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(){
     @Override
        public 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) {
         //基于类方法引用,实例化 Strategy
        Strategy strategy = Unrelated::twice;
        //基于成员方法的引用,实例化 Strategy
        Strategy 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 和 b
        IntBinaryOperator 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 examples
        Bar b = f1.apply(new Foo());
        IBaz ib = f2.apply(11);
        LBaz lb = f3.apply(11);
        DBaz db = f4.apply(11);

        // applyAs* usage examples
        int 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.coloradmin.cn/o/1696253.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

计算机底层技术在AI时代的重要性

AI基础实施 为了迎接AI时代的全面到来&#xff0c;并满足极客星球同学们的学习需求&#xff0c;我们将组织一场AI技术全景入门分享, 会尽我所能&#xff0c;让大家能够全面了解AI技术&#xff0c;从AI大局观到核心技术&#xff0c;希望大家开阔一下AI的视野&#xff0c;本周第…

VMware虚拟机中ubuntu使用记录(10)—— 如何在Ubuntu18.04中使用自己的单目摄像头运行ORB_SLAM3(亲测有效,踩坑记录)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、ORB_SLAM3源码编译二、ORB_SLAM3实时单目相机测试1. 查看摄像头的话题2. 运行测试 三. 运行测试可能的报错1. 报错一(1) 问题描述(2) 原因分析(3) 解决 2. …

python机器学习及深度学习在空间模拟与时间预测

原文链接https://mp.weixin.qq.com/s?__bizMzUyNzczMTI4Mg&mid2247628504&idx2&sn6fe3aeb9f63203cfe941a6bb63b49b85&chksmfa77a9e5cd0020f3aa4f01887e75b15096a182c2b5b42c1044787aa285c650f1469a0ef28aec&token2124656491&langzh_CN&scene21#we…

2024年春招高薪职业报告:大模型算法研究员领跑

近日&#xff0c;脉脉高聘发布的研究报告《2024春招高薪职业和人才洞察》&#xff08;以下简称《洞察》&#xff09;显示&#xff0c;2024年一季度&#xff0c;大模型算法研究员新发岗位以平均月薪6.4万元领跑高薪岗位榜。受人才培养周期和技术门槛影响&#xff0c;人工智能行业…

Django Web:搭建Websocket服务器(入门篇)

Django Web架构 搭建Websocket服务器&#xff08;1&#xff09; - 文章信息 - Author: 李俊才 (jcLee95) Visit me at CSDN: https://jclee95.blog.csdn.netMy WebSite&#xff1a;http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress of this article:htt…

SwiftUI中List的样式及使用详解(添加、移动、删除、自定义滑动)

SwiftUI中的List可是个好东西&#xff0c;它用于显示可滚动列表的视图容器&#xff0c;类似于UITableView。在List中可以显示静态或动态的数据&#xff0c;并支持垂直滚动。List是一个数据驱动的视图&#xff0c;当数据发生变化时&#xff0c;列表会自动更新。 针对List&#x…

SW 弯曲找方向

当旋转弯曲轴的时候,半径和角度 越和理论的接近,越接近(只要输入角度,然后旋转弯曲轴,看半径跟随的变化值)

(优作)风力摆控制系统

本系统由瑞萨 100LGA 单片机控制模块&#xff0c; 6050 三轴陀螺仪加速度模块&#xff0c;直流风机及其驱 动模块&#xff0c;显示模块&#xff0c;键盘模块&#xff0c;蜂鸣器模块以及风力摆机械结构组成&#xff0c; MPU6050 采集风摆姿 态&#xff0c;单片机处理姿态数…

技术速递|无障碍应用程序之旅:键盘可访问性和 .NET MAUI

作者&#xff1a;Rachel Kang 排版&#xff1a;Alan Wang 首先让我们一起来看看您的应用程序是否支持键盘访问&#xff1a; 启动您的其中一个应用。如果您的设备尚未连接物理键盘&#xff0c;请连接物理键盘。像平常一样导航您的应用程序&#xff0c;并且仅使用键盘来执行此操…

Docker提示某网络不存在如何解决,添加完网络之后如何删除?

Docker提示某网络不存在如何解决&#xff1f; 创建 Docker 网络 假设现在需要创建一个名为my-mysql-network的网络 docker network create my-mysql-network运行容器 创建网络之后&#xff0c;再运行 mysqld_exporter 容器。完整命令如下&#xff1a; docker run -d -p 9104…

类和对象【六】友元和内部类

文章目录 友元友元的作用友元的缺点友元函数语法&#xff1a;特点&#xff1a; 友元类语法&#xff1a;特点&#xff1a; 内部类概念特点 友元 友元的作用 友元提供了一种打破封装的方式&#xff0c;有时提供了便利。 友元的主要作用就是打破封装 即可以让一个类的友元函数…

Redis第18讲——Redis和Redission实现延迟消息

即使不是做电商业务的同学&#xff0c;也一定知道订单超时关闭这种业务场景&#xff0c;这个场景大致就是用户下单后&#xff0c;如果在一定时间内未支付&#xff08;比如15分钟、半小时&#xff09;&#xff0c;那么系统就会把这笔订单给关闭掉。这个功能实现的方式有很多种&a…

【限免】短时傅里叶变换时频分析【附MATLAB代码】

来源&#xff1a;微信公众号&#xff1a;EW Frontier 简介 一种能够同时对信号时域和频域分析的方法——短时傅里叶变换&#xff08;STFT&#xff09;&#xff0c;可以在时频二维角度准确地描述信号 的时间、频域的局部特性&#xff0c;与其他算法不同&#xff0c;通过该算法可…

打造游戏APP:面向对象编程的实战演练

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、项目背景与架构概览 二、类的设计与实现 三、面向对象编程的实践 四、游戏循环与事件…

搜索自动补全-elasticsearch实现

1. elasticsearch准备 1.1 拼音分词器 github地址&#xff1a;https://github.com/infinilabs/analysis-pinyin/releases?page6 必须与elasticsearch的版本相同 第四步&#xff0c;重启es docker restart es1.2 定义索引库 PUT /app_info_article {"settings": …

体检系统商业源码,C/S架构的医院体检系统源码,大型健康体检中心管理系统源码

体检系统商业源码&#xff0c;C/S架构的医院体检系统源码&#xff0c;大型健康体检中心管理系统源码 体检信息管理系统软件是对医院体检中心进行系统化和规范化的管理。系统从检前&#xff0c;检中&#xff0c;检后整个业务流程提供标准化以及精细化的解决方案。实现体检业务市…

上位机图像处理和嵌入式模块部署(f103 mcu的最小软件系统)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 我们都知道mcu电路有最小系统。一个最小硬件系统里面包含了mcu、晶振、复位、输入和输出。其实不光硬件如此&#xff0c;软件也有一个最小系统。而…

9.任务调度

一、开启任务调度器 1.函数 vTaskStartScheduler() 函数 vTaskStartScheduler()用于启动任务调度器&#xff0c;任务调度器启动后&#xff0c;FreeRTOS 便会开始 进行任务调度&#xff0c;除非调用函数 xTaskEndScheduler()停止任务调度器&#xff0c;否则不会再返回。函数 vTa…

【对角线遍历】python

没啥思路 class Solution:def findDiagonalOrder(self, mat: List[List[int]]) -> List[int]:mlen(mat)nlen(mat[0])ret[]if len(mat)0:return retcount0#mn-1是对角线总数while count<mn-1:#x和y的和刚好是count数#偶数为右上走if count%20:xcount if(count<m)else (…

Django 里html模板

Django 提供两种方式让程序员自定义html模板。 第一种方法 在项目文件夹里的urls.py进行添加 修改代码如下 from django.contrib import admin from django.urls import path from app01 import views # 得添加这行urlpatterns [path(xxx/, views.home), # 添加这行path(…