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表达式 可作为参数传递给方法,或作为返回值存在变量中
- 简洁:无需像匿名类一样写很多模版代码
语法
示例
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);
- 用于基本类型的特殊函数式接口
- 目的:避免基本类型与包装类自动装箱/拆箱
方法引用与Lambda表达式类型推断
方法引用
- 提供一种间接方式引用已经存在的方法
- 当Lambda表达式只是调用一个现有的方法,可以使用方法引用简化
- 方法引用时一种特殊类型Lambda
- 语法:用分隔符
::表示方法引用,将目标引用放在分隔符::前,方法名放在分隔符::后
构建方法引用实战
- 使用静态方法引用
// 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);