函数式编程入门

101 阅读5分钟

Java8 新特性

  • Iterable interface 中支持 forEach() 方法
  • 在接口中支持默认方法
  • 对于集合框架,Java Stream API 支持大量并行操作
  • Java Time APIs
  • 引入 Optional
  • 并发性能提升:引入 CompletableFuture
  • Java IO 性能提升
  • ……

函数式编程浅析

  • 函数式编程是一种编程范式
  • 使用函数来处理数据和执行计算,而不是改变数据的状态
  • 把运算过程尽量写成一系列嵌套的函数调用 y=f(g(x)) ↔️ y=g(x).f()
  • 函数是一等公民:函数与其他数据类型一样,可赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值

Java 为什么需要函数式编程

  • 自 Java1.0 发布以来,Java 发生的最大变化
  • 引入函数式编程是顺应时代潮流
  • 引入函数式编程,用更少的时间,写更清晰简洁的代码

核心思想

方法Lambda表达式 作为一等公民

函数式编程的核心概念

纯函数

  • 其输出完全由输入决定,并且没有副作用的函数
  • 给定相同的输入,纯函数总是返回相同的输出
  • 不会改变任何外部状态,也不依赖于任何外部状态
  • 副作用是什么?
    • 除了返回值之外,对外部的任何可观测的变化都被称为副作用
    • 修改一个全局变量,修改一个字段、写入文件或打印到控制台都是副作用

不变性

  • 在函数式编程中,数据是不可变的
  • 一个数据结构被创建后就不能被改变
  • 不变性消除了许多常见的错误源,使代码更加简洁,更容易被理解,也更容易进行并发编程

高阶函数

  • 高阶函数:接受其他函数作为参数或返回函数的函数
  • 高阶函数允许将函数作为数据来操作

Lambda表达式 与 函数式接口

简洁表达可传递匿名函数的一种方式

  • 匿名:无需方法名
  • 函数:与普通方法类似,有参数列表,方法体,返回类型,抛出异常列表
  • 可传递:Lambda表达式 可作为参数传递给方法,或作为返回值存在变量中
  • 简洁:无需像匿名类一样写很多模版代码

语法

image.png

示例

image.png

Lambda表达式 的结构说明

  • 一个 Lambda表达式 可以有0个或多个参数
    • 参数的类型既可以明确声明,也可以根据上下文推断
    • 所有参数放在圆括号里,参数之间用逗号相隔
    • 当只有一个参数时,且类型可以推导时圆括号可以省略,如 a -> return a * a
  • Lambda表达式 主体可以有0条或多条语句
    • 如果只有一条语句,花括号{} 可以省略,匿名函数的返回值与主体一致( return可以省略 )
    • 如果包括多条语句,则语句必须包含在花括号中。匿名函数的返回值与主体一致( 必须写 return,除非没有返回值 )

Lambda 能干啥?

  • 最直观的好处:使得代码变得更整洁
  • 可替代只有一个抽象方法的接口实现,告别匿名内部类(函数式接口)
  • 提升了对集合框架的迭代,遍历,过滤等操作

Java常用内置函数接口

这些接口位于 package java.util.function

  • Predicate 接口:
// Predicate函数式接口 传入T类型参数,调用test()方法,返回boolean值
Predicate<Integer> isEven = num -> num % 2 == 0;
System.out.println(isEven.test(100));
System.out.println(isEven.test(99));
  • Function 接口
// Function函数式接口    y = f(x)
Function<String, Integer> StringLengthFunction = str -> str.length();
System.out.println(StringLengthFunction.apply("Hello"));
// andThen链式函数 先求长度再平方(惰性计算:真正调用的时候才调用链式计算)
Function<Integer, Integer> powFunction = num -> num * num;
Function<String, Integer> newFunction = StringLengthFunction.andThen(powFunction);
System.out.println("先求长度再平方: " + newFunction.apply("QWER"));
  • Consume 接口
// Consume函数式接口   消耗掉参数,不返回任何东西
Consumer<String> printUpperCase = s -> System.out.println(s.toUpperCase());
printUpperCase.accept("I am a cat!");
  • Supplier 接口
// Supplier函数式接口  不需要参数,返回一个T类型结果
Supplier<LocalDate> currentDate = () -> LocalDate.now();
Supplier<Integer> currentMonth = () -> LocalDate.now().getMonthValue();
Supplier<Integer> randomInt = () -> new Random().nextInt();
System.out.println("Current Date: " + currentDate.get());
System.out.println("Current Month: " + currentMonth.get());
System.out.println("Random int: " + randomInt.get());
  • 其他常用函数式接口
// BiPredicate函数式接口
BiPredicate<Integer, Integer> biPredicate = (num1, num2) -> num1 > num2;
System.out.println(biPredicate.test(100, 200));

// UnaryOperator函数式接口
UnaryOperator<Integer> powFunc = num -> num * num;
System.out.println("Pow of 100: " + powFunc.apply(100));

// BinaryOperator函数式接口
BinaryOperator<Integer> binaryOperator = (a, b) -> a * 10 + b * 5;
System.out.println("Sum result: " + binaryOperator.apply(10,  5));

// BiConsume函数式接口
BiConsumer<String, Integer> biConsumer = (S, num) -> System.out.println("S: " + S + " Num: " + num);
biConsumer.accept("String", 123);
  • 用于基本类型的特殊函数式接口
    • 目的:避免基本类型与包装类自动装箱/拆箱

image.png

方法引用与Lambda表达式类型推断

方法引用

  • 提供一种间接方式引用已经存在的方法
  • 当Lambda表达式只是调用一个现有的方法,可以使用方法引用简化
  • 方法引用时一种特殊类型Lambda
  • 语法:用分隔符 :: 表示方法引用,将目标引用放在分隔符 :: 前,方法名放在分隔符 ::

image.png

构建方法引用实战

  • 使用静态方法引用
// 1. 静态方法引用
List<String> numbers = Arrays.asList("123", "545", "2", "442", "89", "100");
List<Integer> integers = numbers.stream()
                                .map(Integer::parseInt)
                                .collect(Collectors.toList());
  • 使用实例方法引用
// 2. 实例方法引用
// Car car = new Car();
IntConsumer intConsumer = new Car()::startCar;
intConsumer.accept(50);

List<Integer> lengthList = numbers.stream()
                                  .map(String::length)
                                  .collect(Collectors.toList());
List<Car> carList = new ArrayList<>();
// map里面引用类型,不用实例
List<Integer> speedList = carList.stream()
                                      .map(Car::getSpeed)
                                      .collect(Collectors.toList());
  • 使用构造器方法引用
// 3. 构造器方法引用
// 无参数构造器
Supplier<Car> carSupplier = Car::new;
Car car1 = carSupplier.get();
System.out.println("car1 speed: " + car1.getSpeed());

// 有一个参数构造器
Function<Integer, Car> carFunction = Car::new;
Car car2 = carFunction.apply(50);
System.out.println("car2 speed: " + car2.getSpeed());

构建方法引用误区

错误观点:只能对只有0个或1个参数的方法或构造器进行方法引用

例子:

  • 引用接收两个参数的方法
// 接收两个参数的方法引用
HashMap<String, Integer> hashMap = new HashMap<>();
// BiFunction   String, Integer -> Integer
BiFunction<String, Integer, Integer> putFn = hashMap::put;
putFn.apply("a", 10);
// BiConsumer
BiConsumer<String, Integer> putConsumer = hashMap::put;
putConsumer.accept("b", 100);
System.out.println(hashMap);
  • 调用多参数构造器新建对象

// 多参数构造器方法引用
BiFunction<String, Integer, Car> carBiFunction = Car::new;
Car c = carBiFunction.apply("suv", 100);
System.out.println(c);