请与OOP知识配套使用 效果更佳!
函数式编程
定义
计算=>数学函数 纯函数编程
应用
函数式编程的特性包括:
- 纯函数(Pure Function):纯函数是指具有相同输入始终产生相同输出,并且没有任何副作用的函数。纯函数不会修改传入的参数或者对外部状态进行修改,它仅仅根据输入计算并返回结果,使得代码更加可靠、可测试和可维护。
- 不可变数据(Immutable Data):函数式编程鼓励使用不可变数据,即数据在创建后不可被修改。当需要修改数据时,函数式编程会创建新的数据副本,而不是修改原始数据。这种特性使得代码更易于推理和调试,并且可以避免由于数据共享导致的意外修改问题。
- 函数组合(Function Composition):函数式编程支持将多个函数组合在一起形成新的函数。通过将一个函数的输出作为另一个函数的输入,可以创建更复杂的函数逻辑。这种方式可以提高代码的可重用性和可读性,并且降低了副作用的影响。
- 高阶函数(Higher-Order Function):函数式编程支持高阶函数,即函数可以接受其他函数作为参数或返回函数作为结果。高阶函数可以灵活地组合和操作函数,使得代码更加灵活和模块化。
- 延迟求值(Lazy Evaluation):函数式编程中常用的延迟求值方式是通过惰性计算(Lazy Evaluation)来实现。惰性计算只在真正需要计算结果时才进行计算,可以避免不必要的计算开销,提高性能和效率。
函数式编程的特性使得代码更加模块化、易于理解和测试,并且能够处理并发和并行编程等复杂场景。它强调使用函数作为基本构建块,减少副作用和共享状态,以提高代码的可维护性和可扩展性。函数式编程在许多编程语言中都得到了广泛应用,并成为了现代软件开发中的重要编程范式之一。
具体使用方法
将定义好的匿名函数作为参数传给其他函数或者赋值给变量。
在需要函数的时候,直接定义函数的方法体,而不需要定义显性函数或者实现匿名内部类。
函数式接口
Supplier<T>
:无参数,返回一个结果。它对应的函数方法是T get()
。该接口常用于生成或提供数据,不接收任何输入参数,只产生一个结果。//供给Consumer<T>
:接收一个参数,不返回任何结果。它对应的函数方法是void accept(T t)
。该接口常用于对传入的参数执行某种操作,例如打印、修改状态等。//消耗Function<T, R>
:接收一个参数,返回一个结果。它对应的函数方法是R apply(T t)
。该接口常用于将一个类型的值转换为另一个类型的值,可以用于数据转换、映射等操作。//作用Predicate<T>
:接收一个参数,返回一个布尔值。它对应的函数方法是boolean test(T t)
。该接口常用于对传入的参数进行条件判断,返回布尔值表示是否满足条件。//断言Consumer<String> consumer = (str) -> System.out.println(str); consumer.accept("Hello World!"); Supplier<Integer> supplier = () -> 42; int result = supplier.get(); System.out.println(result); Supplier<Integer> supplier = () -> 42; int result = supplier.get(); System.out.println(result); Supplier<Integer> supplier = () -> 42; int result = supplier.get(); System.out.println(result);
定义
函数式接口是指只包含一个抽象方法的接口。在Java中,函数式接口是用于支持函数式编程的重要概念。
设计目的
支持函数作为参数和返回值的传递,通过lambda表达式直接定义出一个匿名函数或者通过方法引用(lambda表达式)来引用其他地方的方法,并将这个方法直接去实现函数式接口中唯一的抽象方法。
无需关心具体的集合实现类(lambda表达式实现)
函数式
函数性质也是让函数式接口能够成为函数式编程中重要的一部分的一个原因。
1.默认方法和静态方法:不会破坏函数性质,同时又使功能更加丰富。
2.本身的设计目的就是为了支持函数作为返回值或者参数的传递
3.只包含一个抽象方法,表明这个接口实现了单一的函数行为,通过输入参数,进行计算,得出结果。
其他特点
注解标记:函数式接口可以使用@FunctionalInterface
注解进行标记。该注解是可选的,但推荐使用。它提供了编译器级别的检查,确保接口仅包含一个抽象方法,以免引入错误。
Java标准库中的许多功能接口,如Runnable
、Comparator
、Supplier
等,都是函数式接口的示例。此外,Java 8引入的java.util.function
包提供了一组用于常见函数式接口的接口定义。
以下是一个简单的函数式接口示例:
javaCopy code@FunctionalInterface
interface MyFunction {
void doSomething();
}
使用Lambda表达式可以实例化该函数式接口:
javaCopy codeMyFunction func = () -> {
System.out.println("Doing something...");
};
func.doSomething();
应用
- 函数式编程:如果希望采用函数式编程的思维方式,使用纯函数和不可变数据,函数式接口是必不可少的。函数式接口提供了函数描述符,可以定义函数的输入和输出,并通过Lambda表达式或方法引用来实现具体的函数。
- 回调机制:当需要在特定事件发生时执行回调操作时,可以使用函数式接口来定义回调函数。通过函数式接口,可以将回调函数作为参数传递给需要监听事件的方法,并在事件发生时调用回调函数。
- 集合操作:函数式接口在集合操作中非常有用。例如,Java 8引入的Stream API就使用了函数式接口来进行集合的转换、过滤、映射等操作。通过使用函数式接口,可以以更简洁和声明式的方式对集合进行操作。
- 并行处理:函数式接口对于并行处理非常有用。Java 8引入的并行流和CompletableFuture等并发工具使用了函数式接口来描述并行执行的任务和操作。通过使用函数式接口,可以在多个线程上执行操作,并利用并行性来提高程序的性能。
Lambda表达式
定义
lambda表达式是用于定义一个无返回值,修饰符,其间包括参数列表,方法体的匿名函数的函数式编程特性。
用途
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// 使用Lambda表达式打印每个名称
names.forEach(name -> System.out.println(name));//多行语句要括号
// 使用方法引用简化Lambda表达式
names.forEach(System.out::println);
//如果Lambda表达式不需要参数,可以省略参数列表
() -> System.out.println("Hello")
MyFunction func = () -> {
System.out.println("Doing something...");
};
func.doSomething(); // 调用抽象方法
//单个参数
最简单的Lambda表达式由参数列表、箭头符号(->)和表达式体组成
(int x)->x*x;
lambda特性分支之方法引用
方法引用 适用于 Function/Consumer接口。
list.stream().map(String::toUpperCase).forEach(e->System.out.println(e));
允许直接引用现有的方法或构造函数
格式:A(实例,类名 :: 构造方法,实例方法,静态方法)
方法引用的语法有以下几种形式:
- 静态方法引用:
ClassName::staticMethodName
例如:Integer::parseInt
表示引用了Integer
类的parseInt
静态方法。 - 实例方法引用:
instance::instanceMethodName
例如:str::length
表示引用了字符串str
的length
实例方法。 - 类的任意对象的实例方法引用:
ClassName::instanceMethodName
例如:String::toUpperCase
表示引用了String
类的toUpperCase
实例方法。 - 构造函数引用:
ClassName::new
例如:ArrayList::new
表示引用了ArrayList
类的构造函数。
当使用方法引用时,我们可以将已存在的方法引用赋给函数式接口的实例,从而实例化该函数式接口。这是因为函数式接口只有一个抽象方法,我们可以通过方法引用将现有的方法与该抽象方法进行关联,从而创建函数式接口的实例。
例如,假设有一个函数式接口Converter
,它包含一个抽象方法convert
:
javaCopy code@FunctionalInterface
interface Converter<T, R> {
R convert(T input);
}
我们可以使用方法引用来实例化这个接口,将已存在的方法与convert
方法进行关联。具体的方法引用取决于我们要引用的方法的签名和情况。
静态方法引用:
javaCopy code Converter<String, Integer> converter = Integer::parseInt;
实例方法引用:
javaCopy codeString str = "Hello, world!"; Converter<String, Integer> converter = str::length;
类的任意对象的实例方法引用:
javaCopy codeList<String> list = Arrays.asList("A", "B", "C"); Converter<List<String>, Integer> converter = List::size;
通过方法引用,我们将已存在的方法与函数式接口的抽象方法进行绑定,实例化了该函数式接口。此时,我们可以通过调用该函数式接口的方法来执行与方法引用关联的方法。
此外,方法引用还可以在方法的传递和处理中使用。我们可以将一个接收函数式接口作为参数的方法,通过方法引用传递一个已存在的方法。这样,方法在被调用时就会执行与方法引用关联的方法。这种方式使得代码更加简洁和易读。
例如:
javaCopy codeList<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// 使用方法引用将toUpperCase方法传递给map方法
List<String> upperCaseNames = names.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
在上述示例中,通过方法引用String::toUpperCase
将字符串的toUpperCase
方法传递给map
方法,将每个字符串转换为大写形式。
这样,方法引用可以在函数式接口的实例化、方法的传递和处理中起到简化代码的作用,并提高代码的可读性和可维护性。
应用场景
排序(详见排序和比较.md),过滤,映射
过滤
List<Integer> numList = Arrays.asList(5,2,3,7,9);
List<Integer> evenNumList = numList.stream().
filter(e->e%2==0).
collect(Collectors.toList());
映射
List<String> nameList = Arrays.asList("Henry","Lisa","Bob");
List<String> upperCaseNameList = nameList.stream().
map(String::toUpperCase).
collect(Collectors.toList());