Java的函数式编程详解
Java的函数式编程
在Java中,函数式编程的支持主要是通过引入Java 8中的Lambda表达式和Stream API来实现的。Lambda表达式允许开发者以更简洁的方式编写匿名函数,从而促进函数式编程的实践。Stream API则提供了一种方便的方式来处理集合数据,支持流水线式的数据处理操作。
以下是Java中函数式编程的一些主要特性和用法:
Lambda表达式:Lambda表达式允许你直接以更简洁的方式传递函数作为参数,例如:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
numbers.forEach(number -> System.out.println(number));
Stream API:Stream API 提供了一种处理集合数据的流式操作方式,支持各种操作,如过滤、映射、归约等,例如:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Emma");
long count = names.stream()
.filter(name -> name.startsWith("A"))
.count();
System.out.println("Names starting with 'A': " + count);
方法引用:方法引用允许你使用已有方法的引用来代替 Lambda 表达式,例如:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Emma");
names.forEach(System.out::println);
函数式接口:函数式接口是只包含一个抽象方法的接口,Lambda 表达式可以被赋给这种接口的实例,例如:
@FunctionalInterface
interface MyFunction {
void doSomething();
}
MyFunction myFunction = () -> System.out.println("Doing something...");
myFunction.doSomething();
Java的函数式编程特性使得代码更加简洁、可读性更强,同时也提供了一种更灵活的编程风格来处理集合数据和函数传递。
FunctionalInterface接口
在Java8中引入了一个新接口FunctionalInterface
,FunctionalInterface
接口是一个特殊的接口,它声明了只包含一个抽象方法的接口。该接口用于支持Lambda表达式,因为Lambda表达式需要与函数式接口(只有一个抽象方法的接口)兼容。
FunctionalInterface
接口本身并没有定义任何方法,它只是作为一种标记接口存在。Java编译器会在编译时检查一个接口是否符合函数式接口的标准:是否只包含一个抽象方法。如果是,则该接口会被视为函数式接口,可以使用Lambda表达式来实例化它。
在使用FunctionalInterface
时,通常会给接口添加@FunctionalInterface
注解,这样做的好处是可以确保该接口只包含一个抽象方法,同时提供了更好的文档说明。
以下是一个示例:
@FunctionalInterface
interface MyFunctionalInterface {
void myMethod();
}
如果在MyFunctionalInterface
接口中添加了第二个抽象方法,编译器就会报错,因为@FunctionalInterface
注解要求该接口只包含一个抽象方法。
函数式接口使得Java中的函数式编程更加便捷和灵活,能够更好地支持Lambda表达式的使用。
函数式接口与Lambda表达式
当我们定义的接口只有一个抽象方法
时,这个接口默认添加FunctionalInterface
接口即可在业务中使用Lambda表达式
进行实现,当我们对一个接口添加FunctionalInterface
接口时即表示这个类将被定义为函数时接口,此时这个接口只允许存在且只存在一个抽象方法,并且此抽象方法不能添加默认实现。
方法引用
方法引用需要使用到Java内建的函数式接口。
内建的函数式接口
在Java中,有一些内建的函数式接口,它们提供了不同的功能和用途,常用的包括:
Supplier<T>: 代表一个供应商,它不接受任何参数,但返回一个值。常用于延迟计算或生成值的场景。
Supplier<String> supplier = () -> "Hello";
String result = supplier.get(); // 获取供应商提供的值
Consumer<T>: 代表一个消费者,它接受一个参数并且不返回任何结果。常用于对集合元素进行迭代或执行副作用的场景。
Consumer<String> consumer = (s) -> System.out.println("Consuming: " + s);
consumer.accept("Hello"); // 执行消费者的操作
Function<T, R>: 代表一个函数,它接受一个参数并返回一个结果。常用于数据转换或映射的场景。
Function<Integer, String> function = (i) -> "Number: " + i;
String result = function.apply(42); // 对参数进行转换操作
Predicate<T>: 代表一个断言,它接受一个参数并返回一个布尔值,常用于过滤或条件判断的场景。
Predicate<Integer> predicate = (i) -> i > 0;
boolean result = predicate.test(10); // 对参数进行断言判断
UnaryOperator<T>: 是Function<T, T>的一个特例,代表一个一元运算符,它接受一个参数并返回相同类型的结果。常用于对单个值进行操作的场景。
UnaryOperator<Integer> operator = (i) -> i * i;
int result = operator.apply(5); // 对参数进行一元操作
BinaryOperator<T>: 是BiFunction<T, T, T>的一个特例,代表一个二元运算符,它接受两个参数并返回相同类型的结果。常用于对两个值进行操作的场景。
BinaryOperator<Integer> operator = (a, b) -> a + b;
int result = operator.apply(10, 20); // 对两个参数进行二元操作
这些内建的函数式接口提供了在函数式编程中常见的操作,使得在Java中更容易地实现函数式风格的编程。
方法引用的使用
方法引用是一种简化Lambda表达式的语法,用于直接引用现有方法或构造函数。它可以让你直接使用已有方法的名称来替代Lambda表达式中的具体实现,使得代码更加简洁、清晰和易于理解。
Java中的方法引用有四种主要形式:
静态方法引用: 使用类名来引用静态方法。
// Lambda表达式
Function<String, Integer> parseInt = (s) -> Integer.parseInt(s);
// 方法引用
Function<String, Integer> parseInt = Integer::parseInt;
实例方法引用: 使用对象实例来引用实例方法。
// Lambda表达式
Consumer<String> printUpperCase = (s) -> System.out.println(s.toUpperCase());
// 方法引用
Consumer<String> printUpperCase = System.out::println;
对象方法引用: 使用特定对象的方法引用实例方法。
// Lambda表达式
Function<String, String> toUpperCase = (s) -> s.toUpperCase();
// 方法引用
Function<String, String> toUpperCase = String::toUpperCase;
构造函数引用: 使用类名来引用构造函数。
// Lambda表达式
Supplier<ArrayList<String>> arrayListSupplier = () -> new ArrayList<>();
// 方法引用
Supplier<ArrayList<String>> arrayListSupplier = ArrayList::new;
在方法引用中,::
操作符用于分隔类名或对象实例和方法名称。方法引用的选择取决于Lambda表达式的实现,如果Lambda表达式仅仅调用了一个已经存在的方法,那么使用方法引用会使代码更加简洁和清晰。
Stream流
Java Stream 是 Java 8 引入的一个新的抽象层,用于处理集合数据的函数式编程工具。它提供了一种更为简洁、灵活和可读性更强的方式来处理集合数据。
在java.util.stream.Stream类中包含了我们所有对流的操作方法。详见:关于Java Stream流
所有中间操作
方法 | 说明 |
---|---|
sequential | 返回一个相等的串行的Stream对象,如果原Stream对象已经是串行就可能会返回原对象 |
parallel | 返回一个相等的并行的Stream对象,如果原Stream对象已经是并行的就会返回原对象 |
unordered | 返回一个不关心顺序的Stream对象,如果原对象已经是这类型的对象就会返回原对象 |
onClose | 返回一个相等的Steam对象,同时新的Stream对象在执行Close方法时会调用传入的Runnable对象 |
close | 关闭Stream对象 |
filter | 元素过滤:对Stream对象按指定的Predicate进行过滤,返回的Stream对象中仅包含未被过滤的元素 |
map | 元素一对一转换:使用传入的Function对象对Stream中的所有元素进行处理,返回的Stream对象中的元素为原元素处理后的结果 |
mapToInt | 元素一对一转换:将原Stream中的使用传入的IntFunction加工后返回一个IntStream对象 |
flatMap | 元素一对多转换:对原Stream中的所有元素进行操作,每个元素会有一个或者多个结果,然后将返回的所有元素组合成一个统一的Stream并返回; |
distinct | 去重:返回一个去重后的Stream对象 |
sorted | 排序:返回排序后的Stream对象 |
peek | 使用传入的Consumer对象对所有元素进行消费后,返回一个新的包含所有原来元素的Stream对象 |
limit | 获取有限个元素组成新的Stream对象返回 |
skip | 抛弃前指定个元素后使用剩下的元素组成新的Stream返回 |
takeWhile | 如果Stream是有序的(Ordered),那么返回最长命中序列(符合传入的Predicate的最长命中序列)组成的Stream;如果是无序的,那么返回的是所有符合传入的Predicate的元素序列组成的Stream。 |
dropWhile | 与takeWhile相反,如果是有序的,返回除最长命中序列外的所有元素组成的Stream;如果是无序的,返回所有未命中的元素组成的Stream。 |
所有终端操作
方法 | 说明 |
---|---|
iterator | 返回Stream中所有对象的迭代器; |
spliterator | 返回对所有对象进行的spliterator对象 |
forEach | 对所有元素进行迭代处理,无返回值 |
forEachOrdered | 按Stream的Encounter所决定的序列进行迭代处理,无返回值 |
toArray | 返回所有元素的数组 |
reduce | 使用一个初始化的值,与Stream中的元素一一做传入的二合运算后返回最终的值。每与一个元素做运算后的结果,再与下一个元素做运算。它不保证会按序列执行整个过程。 |
collect | 根据传入参数做相关汇聚计算 |
min | 返回所有元素中最小值的Optional对象;如果Stream中无任何元素,那么返回的Optional对象为Empty |
max | 与Min相反 |
count | 所有元素个数 |
anyMatch | 只要其中有一个元素满足传入的Predicate时返回True,否则返回False |
allMatch | 所有元素均满足传入的Predicate时返回True,否则False |
noneMatch | 所有元素均不满足传入的Predicate时返回True,否则False |
findFirst | 返回第一个元素的Optioanl对象;如果无元素返回的是空的Optional; 如果Stream是无序的,那么任何元素都可能被返回。 |
findAny | 返回任意一个元素的Optional对象,如果无元素返回的是空的Optioanl。 |
isParallel | 判断是否当前Stream对象是并行的 |
Optional 空值处理类
Optional 是什么
Optional
类是Java 8引入的一个用于处理可能为null的值的容器类。它的设计目的是帮助开发者更加优雅地处理可能为空的情况,避免出现空指针异常(NullPointerException)。
Optional
类的主要特点和用途包括:
避免空指针异常: 通过使用
Optional
类,可以在代码中明确表达一个值可能为null的情况,从而避免因为空指针而引发的异常。强制显式处理可能为空的值: 当你从一个方法返回一个
Optional
类型的值时,就强制调用者考虑可能为空的情况,并显式地处理这种情况。提供一组操作方法:
Optional
类提供了一组方法来处理可能为空的值,例如isPresent()
用于检查值是否存在、orElse()
用于在值为空时提供一个默认值、orElseGet()
用于在值为空时通过一个Supplier提供一个默认值、orElseThrow()
用于在值为空时抛出一个异常等等。更加清晰的API设计: 通过使用
Optional
类,可以使得API更加清晰地表达可能为空的情况,提高代码的可读性和可维护性。与流式操作结合使用:
Optional
类与流式操作(Stream API)可以很好地结合使用,例如在流操作中可能会产生为空的情况,使用Optional
类可以更加优雅地处理这种情况。
总的来说,Optional
类是Java中处理可能为空的值的一种更加安全和优雅的方式,它有助于减少空指针异常的发生,并使得代码更加健壮和易于理解。
常用方法
方法 | 作用 |
---|---|
get | 获取Value的值,如果Value值是空值,则会抛出NoSuchElementException异常;因此返回的Value值无需再做空值判断,只要没有抛出异常,都会是非空值。 |
isPresent | Value是否为空值的判断; |
ifPresent | 当Value不为空时,执行传入的Consumer; |
ifPresentOrElse | Value不为空时,执行传入的Consumer;否则执行传入的Runnable对象; |
filter | 当Value为空或者传入的Predicate对象调用test(value)返回False时,返回Empty对象;否则返回当前的Optional对象 |
map | 一对一转换:当Value为空时返回Empty对象,否则返回传入的Function执行apply(value)后的结果组装的Optional对象; |
flatMap | 一对多转换:当Value为空时返回Empty对象,否则传入的Function执行apply(value)后返回的结果(其返回结果直接是Optional对象) |
or | 如果Value不为空,则返回当前的Optional对象;否则,返回传入的Supplier生成的Optional对象; |
stream | 如果Value为空,返回Stream对象的Empty值;否则返回Stream.of(value)的Stream对象; |
orElse | Value不为空则返回Value,否则返回传入的值; |
orElseGet | Value不为空则返回Value,否则返回传入的Supplier生成的值; |
orElseThrow | Value不为空则返回Value,否则抛出Supplier中生成的异常对象; |