Java 8 函数式编程
2021-06-21 21:34:30 35 举报
AI智能生成
Java8函数式编程(未完待续)
作者其他创作
大纲/内容
Lambda表达式
去除大量模板代码,使代码变得紧凑,增强可读性
表现形式
(类型 参数) -> {方法体}
例如
BinaryOperator<Long> add = (x, y) -> x + y;
BinaryOperator<Long> addExplicit = (Long x, Long y) -> x + y;
Runnable task = () -> {System.out.println("hello lambda");};
ActionListener oneArgument = event -> System.out.println("button clicked");
BinaryOperator<Long> add = (x, y) -> x + y;
BinaryOperator<Long> addExplicit = (Long x, Long y) -> x + y;
Runnable task = () -> {System.out.println("hello lambda");};
ActionListener oneArgument = event -> System.out.println("button clicked");
类型可选,一般javac会自动推断出参数类型(也存在无法推断的情况)
没有参数时,省略类型和参数
有且仅有一个参数时,参数括号可省略
方法体如果只有一行语句,可省略花括号
没有参数时,省略类型和参数
有且仅有一个参数时,参数括号可省略
方法体如果只有一行语句,可省略花括号
方法引用
类名::方法名
Function<Integer, String> intToStr = String::valueOf; // 等同于 e -> String.valueOf(e)
当参数仅有一个,且返回值为类的一个方法(静态或非静态均可)返回值时,可省略参数,使用 类名::方法名 的方式描述
使用 类名::new 来引用类的构造方法
Function<char[], String> newStrFromCharArray = String::new; // 调用的是 new String(char[] cs)构造方法
String[]::new 来引用数组的构造方法
String[]::new 来引用数组的构造方法
值引用
同匿名内部类类似,在Lambda表达式中引用的变量,需要声明为final或在既成事实上为final
即终态变量
即终态变量
String name = "Alex";
// name自声明以来仅赋值一次,
// 虽然没有显式声明为final,但为既成事实上的final变量
button.addActionListener(event -> System.out.println("hi " + name));
name = "Jack"; // 再次赋值会导致上一行lambda表达式报错
// name自声明以来仅赋值一次,
// 虽然没有显式声明为final,但为既成事实上的final变量
button.addActionListener(event -> System.out.println("hi " + name));
name = "Jack"; // 再次赋值会导致上一行lambda表达式报错
此特性表明lambda表达式中变量实际上为值引用
函数式接口
函数式接口是只有一个抽象方法的接口,用作Lambda表达式的类型
允许存在其他非抽象方法(如静态非抽象方法、default默认方法)
允许存在其他非抽象方法(如静态非抽象方法、default默认方法)
只有一个抽象方法的限制保证了javac可以正常的推断Lambda表达式所实现的接口方法
(可选)可以使用@FunctionalInterface标注函数式接口,该注解会强制javac检查一个接口是否符合函数接口的标准。如果该注释添加给一个枚举类型、类或另一个注释,或者接口包含不止一个抽象方法,javac就会报错。重构代码时,使用它能很容易发现问题
常用的函数式接口
java.util.function
java.util.function
一元接口
Predicate<T> :
boolean test(T t)
谓词函数,传入一个变量,根据一定规则进行布尔判断
谓词函数,传入一个变量,根据一定规则进行布尔判断
Function<T, R>
R apply(T t)
一般函数,传入一个变量,输出另一个变量
一般函数,传入一个变量,输出另一个变量
UnaryOperator<T>
T apply(T t)
算子函数,输入输出同类型
形如XXOperator的接口均为算子函数式接口,入参出参类型相同
算子函数,输入输出同类型
形如XXOperator的接口均为算子函数式接口,入参出参类型相同
Consumer<T>:
void accept(T t)
消费函数,传入一个变量,执行无返回值的操作
消费函数,传入一个变量,执行无返回值的操作
Supplier<T>
T get()
生产函数,没有输入,输出一个变量
生产函数,没有输入,输出一个变量
二元接口
BiPredicate<T, U>
boolean test(T t, U u)
谓词函数的二元版本,接受两个参数
谓词函数的二元版本,接受两个参数
BiFunction<T, U, R>
R apply(T t, U u)
一般函数的二元版本,接受两个参数
一般函数的二元版本,接受两个参数
BinaryOperator<T>
T apply(T t1, T t2)
算子函数的二元版本,接受两个参数
算子函数的二元版本,接受两个参数
BiConsumer<T, U>
void accept(T t, U u)
消费函数的二元版本,接受两个参数
消费函数的二元版本,接受两个参数
Function, Predicate, Consumer等及其二元版本还有一些default默认方法也较为常用,待补充
Predicate, Fucntion, Consumer, Supplier, UnaryOperator等及其二元版本均有int, double, long版本的函数式接口
详见java.util.function包
详见java.util.function包
类型推断
javac会根据应用上下文自动推断变量类型
final String[] ary = {"Hello", "World"};
Map<String, Integer> diamondWordCounts = new HashMap<>();
Map
Predicate<Integer> atLeast5 = x -> x > 5;
依据Predicate<Integer>自动推断x的类型为Integer,并且表达式的返回值为boolean
反例
BinaryOperator add = (x, y) -> x + y;
编译报错,因为无法推断xy的类型,则认定xy类型为Object,而Object类型不支持+操作符
流(Stream)
外部迭代到内部迭代
外部迭代:针对集合的普通for循环,由应用代码控制迭代的方式
本质上为串行化操作,不易修改为并行化迭代
本质上为串行化操作,不易修改为并行化迭代
内部迭代:通过对集合的stream操作,应用程序仅仅关心迭代需要进行的操作和迭代结果
迭代的实现方式由集合自行控制
迭代的实现方式由集合自行控制
实现方式
Collection的stream方法返回集合的一个可操作流对象(并非创建了一个新的集合)
针对stream有一系列的流操作方法,分为两大类
惰性求值
仅仅是对流需要执行的操作的描述,并不会立即执行
返回值是stream
返回值是stream
例如过滤操作filter
及早求值
从stream产生新值的方法,会导致stream的迭代
返回值是非stream或空
返回值是非stream或空
例如计数操作count
针对如下例子:
List<String> strings = ...;
long emptyStrCount = strings
.stream()
.filter(str -> !str.isEmpty())
.count();
List<String> strings = ...;
long emptyStrCount = strings
.stream()
.filter(str -> !str.isEmpty())
.count();
strings为一个字符串列表,对其中为空的字符串进行计数
通过stream()方法获取集合的可操作stream流
filter方法接受一个一元谓词函数(Predicate<String>),并依据返回的boolean值决定断言的元素是否保留到过滤后的stream中
filter操作并不会立即执行,它仅仅描述需要对strings.stream()这个流对象需要执行的操作
filter操作并不会立即执行,它仅仅描述需要对strings.stream()这个流对象需要执行的操作
最后count方法对过滤后的stream进行元素计数,获得最终结果
如果修改为
strings.stream().filter(str -> {
System.out.println(str);
return str.isEmpty();
});
此代码执行后并不会有任何输出,因为filter方法不会构造一个新的列表,不会立即迭代列表,
并且此stream没有及早求值方法,因此不会进行迭代,即filter方法不会被执行
strings.stream().filter(str -> {
System.out.println(str);
return str.isEmpty();
});
此代码执行后并不会有任何输出,因为filter方法不会构造一个新的列表,不会立即迭代列表,
并且此stream没有及早求值方法,因此不会进行迭代,即filter方法不会被执行
常用的流操作
惰性求值操作
filter
接收一个Predicate<T>,并据此对stream执行过滤,仅保留test(T t)结果为true的元素
map
接收一个Function<T, R>,并据此对stream中每个元素T进行处理,将返回结果R组成新的stream
例如
List<String> intStr =
integers.stream().map(String::valueOf).collect(Collectors.toList());
List<String> intStr =
integers.stream().map(String::valueOf).collect(Collectors.toList());
flatMap
将多个stream流中的元素综合成一个stream
例如
List<Integer> intList =
Stream.of(Arrays.asList(1,2), Arrays.asList(3,4))
.flatMap(e -> e.stream()).collect(Collectors.toList());
将[1,2]和[3,4]两个列表合并成一个列表[1,2,3,4],e -> e.stream()可替换为Collection::stream
List<Integer> intList =
Stream.of(Arrays.asList(1,2), Arrays.asList(3,4))
.flatMap(e -> e.stream()).collect(Collectors.toList());
将[1,2]和[3,4]两个列表合并成一个列表[1,2,3,4],e -> e.stream()可替换为Collection::stream
distinct
从stream元素中去除重复元素,判断依据equals方法的返回值
例如
int distinctValCount = integers.stream().distinct().count(); // 求取整数列表中不重复元素的个数
int distinctValCount = integers.stream().distinct().count(); // 求取整数列表中不重复元素的个数
sorted
依据自然顺序(natural order)对stream元素排序,返回排序后的stream
例如
List<Integer> sortedInt = integers.stream().sorted().collect(Collectors.toList());
求取整数列表排序后的列表
List<Integer> sortedInt = integers.stream().sorted().collect(Collectors.toList());
求取整数列表排序后的列表
元素需要实现Comparable<T>接口,或者给sorted传入自定义的Comparable<T>
peek
接受一个Consumer<T>,对stream中的每个元素执行Consumer操作,并返回原stream
例如
List sortedInt = integers.stream()
.peek(System.out::println)
.sorted()
.peek(System.out::println)
.collect(Collectors.toList());
求取整数列表排序后的列表,并将排序前后的元素打印出来
List
.peek(System.out::println)
.sorted()
.peek(System.out::println)
.collect(Collectors.toList());
求取整数列表排序后的列表,并将排序前后的元素打印出来
通常用于对数据进行监控,日志记录等
limit/skip
接受一个long类型参数,限制保留/跳过指定数量的元素
例如
// 求取整数数列中前三小的数
List min3int = integers.stream().sorted().limit(3).collect(Collectors.toList());
// 去除整数数列中前三小的数
List min3int = integers.stream().sorted().skip(3).collect(Collectors.toList());
// 求取整数数列中前三小的数
List min3int = integers.stream().sorted().limit(3).collect(Collectors.toList());
// 去除整数数列中前三小的数
List min3int = integers.stream().sorted().skip(3).collect(Collectors.toList());
及早求值操作
collect
对stream执行收集操作,产生一个新的对象(集合)
接受一个实现了Collector接口的对象作为参数,用来描述收集元素形成集合的方式
接受一个实现了Collector接口的对象作为参数,用来描述收集元素形成集合的方式
常用stream.collect(Collectors.toList())来从stream中获取列表
Collectors类中预定义了许多常用的集合收集方法
Collectors类中预定义了许多常用的集合收集方法
例如
List<String> simpleStrings =
Stream.of("1", "2", "3").collect(Collectors.toList());
一行代码快速构造简单列表
与Arrays.asList("1", "2", "3")结果相同(equal)
List<String> simpleStrings =
Stream.of("1", "2", "3").collect(Collectors.toList());
一行代码快速构造简单列表
与Arrays.asList("1", "2", "3")结果相同(equal)
reduce
从一个stream的一组元素中生成一个值
例如
int sum = integers.stream().reduce(0, (m, n) -> m + n); // 对整数列表求和
int product = integers.stream().reduce(1, (m, n) -> m * n); // 对整数列表求积
int result = integers.stream().reduce(0, (m, n) -> m + n * n); // 求平方和
String bigStr = strings.stream().reduce("", (m, n) -> m + n); // 将字符串列表合并成一个字符串
BinaryOperator<T> 中第一个参数(上例中m)为累积结果,第二个参数(上例中n)为stream中的遍历元素
int sum = integers.stream().reduce(0, (m, n) -> m + n); // 对整数列表求和
int product = integers.stream().reduce(1, (m, n) -> m * n); // 对整数列表求积
int result = integers.stream().reduce(0, (m, n) -> m + n * n); // 求平方和
String bigStr = strings.stream().reduce("", (m, n) -> m + n); // 将字符串列表合并成一个字符串
BinaryOperator<T> 中第一个参数(上例中m)为累积结果,第二个参数(上例中n)为stream中的遍历元素
接受一个初始值T,和一个BinaryOperator<T>,随后从初始值开始,对stream中的元素逐个执行二元操作
常用reduce操作
max/min
求stream中“最大”/“最小”的元素,接受一个Comparator<T>
例如
Integer maxVal = integers.stream().max((m, n) -> m.compareTo(n)).get();
Integer maxVal = integers.stream().max(Integer::compareTo).get();
求取最大的整数
Integer maxVal = integers.stream().max((m, n) -> m.compareTo(n)).get();
Integer maxVal = integers.stream().max(Integer::compareTo).get();
求取最大的整数
返回一个Optional<T>,详见下文
类Comparator提供了许多辅助方法
例如
String longestStr = strings.stream().max(Comparator.comparing(ste -> str.length())).get();
获取列表中最长的字符串
String longestStr = strings.stream().max(Comparator.comparing(ste -> str.length())).get();
获取列表中最长的字符串
count
对stream中的元素计数
anyMatch/allMatch/noneMatch
接受一个Predicate<T>,依据Predicate判断stream元素中是否 存在任何满足/所有都满足/没有满足 指定断言的元素
返回布尔值
返回布尔值
例如
boolean hasEmptyStr = strings.stream().anyMatch(String::isEmpty);
判断列表中是否存在空字符串
boolean hasEmptyStr = strings.stream().anyMatch(String::isEmpty);
判断列表中是否存在空字符串
findFirst/findAny
获取stream中第一个/任意一个元素,返回Optional<T>
forEach
接受一个Consumer<T>,对stream中的每个元素执行制定操作,返回void,此stream终止
forEachOrder
此操作会按照stream中的原顺序进行Consumer操作,适用于使用了并行流(parallel)的stream
并行流 parallel stream
集合的stream()方法返回的是串行的流,对其的操作一般都是串行化的
对集合调用parallelStream()方法,或者对steram调用parallel()方法,可以获取并行化的流
并行化的流使用ForkJoinPool.commonPool()提供的pool来执行并行化任务
pool的大小默认为处理器核心数量,可以通过-Djava.util.concurrent.ForkJoinPool.common.parallelism=N来控制
对任务简单,可并行的操作,可以通过并行流来提高执行效率,但是对于重型任务,耗时较久的任务,需要慎重考虑;
因为java默认使用ForkJoinPool.commonPool()来执行parallel stream中的操作任务,并且后来的任务不会取消前置的耗时任务,
这会导致,如果之前正在对并行流执行重度任务,后来的并行流任务将会等待(当pool已经满载时)
因为java默认使用ForkJoinPool.commonPool()来执行parallel stream中的操作任务,并且后来的任务不会取消前置的耗时任务,
这会导致,如果之前正在对并行流执行重度任务,后来的并行流任务将会等待(当pool已经满载时)
链式调用
使用一个例子来说明stream的链式调用
TODO
高阶函数
高阶函数是指接受另外一个函数作为参数,或返回一个函数的函数
要点解析
基本类型
java中的集中基本类型都有其对应的对象类型,基本类型转换为对象类型称之为装箱,反之为拆箱
当在集合中处理基本类型时,都需要引用其对应的装箱类型,例如一个整数列表 List<Integer>,
然而在对装箱类型做大量的操作时(比如+)会频繁的装箱和拆箱,降低效率,
可以将此种对象类型的stream转换成基本类型流之后再操作
然而在对装箱类型做大量的操作时(比如+)会频繁的装箱和拆箱,降低效率,
可以将此种对象类型的stream转换成基本类型流之后再操作
对于int、long和double这三个基本类型,标准库已经提供了对应的高阶映射方法和对应的stream
IntStream, LongStream, DoubleStream
经过特殊处理的流,专用做处理int、long、double等常用数据流,比装箱类型的流更加高效
除了基本的流操作之外,提供了例如sum、max、min、average、summaryStatistics等数值计算方法
除了基本的流操作之外,提供了例如sum、max、min、average、summaryStatistics等数值计算方法
mapToInt, mapToLong, mapToDouble
分别接受 ToIntFunction<T>, ToLongFunction<T>, ToDoubleFunction<T>
将一个流转换为IntStream, LongStream, DoubleStream
将一个流转换为IntStream, LongStream, DoubleStream
重载解析
Lambda表达式作为参数时,其类型由它的目标类型推导得出,推导过程遵循如下规则
如果只有一个可能的目标类型,由相应函数接口里的参数类型推导得出
如果有多个可能的目标类型,由最具体的类型推导得出
如果有多个可能的目标类型且最具体的类型不明确,则需人为指定类型
默认方法
类胜于接口 如果在继承链中有方法体或抽象的方法声明,那么就可以忽略接口中定义的方法
保证代码向后兼容
子类胜于父类 如果一个接口继承了另一个接口,且两个接口都定义了一个默认方法,那么子类中定义的方法胜出
如果上面两条规则不适用,子类要么需要实现该方法,要么将该方法声明为抽象方法
Optional
Optional<T>可以看做一个容器,用来代替null值
null值通常会引发NullPointerException,Optional就是为了更加安全的处理null值
null值通常会引发NullPointerException,Optional就是为了更加安全的处理null值
构造
通过Optional.of(T t)工厂方法来获取一个包装了t的Optional对象,要求t非空
通过Optional.ofNullable(T t)工厂方法来获取一个允许t为空的对象
Optional.empty()返回一个空的Optional对象
使用
isPresent()方法判断容器是否为空
ifPresent(Consumer<T> consumer)方法判断容器是否为空,如果不为空,则使用Consumer处理对象,否则不处理
get()方法获取容器中的对象,需要首先判断对象是否存在,如果对象为null,会抛出异常
orElse(T other)方法获取容器中的对象,如果对象为null,则返回提供的默认值other
orElseGet(Supplier<T> other)方法获取容器中的对象,如果对象为null,则通过调用Supplier的get方法获取默认值
filter(Predicate<T> predicate)方法使用提供的断言来检测容器中的对象,
如果检测为true,则返回原Optional对象,否则返回空的Optional对象
如果检测为true,则返回原Optional对象,否则返回空的Optional对象
map(Function<T, U> mapper)方法依据mapper将对象转换为另一个对象
stream元素顺序
如果入流是有序的,则出流也是有序的
如从List得到的流
如果入流是无序的,则出流也是无序的
如从Set得到的流
对于forEach来说,无法保证并行流的顺序是有序的,可以使用forEachOrder来保证按照入流顺序处理
收集器
转换成其他集合
转换为值
参考
https://blog.csdn.net/f641385712/article/details/81506090
0 条评论
下一页