1. Java 8 给 Collection
接口增加的两个默认方法
default Stream<E> stream() { return StreamSupport.stream(spliterator(), false); }
:返回一个顺序流,顺序流就是由一个线程执行操作default Stream<E> parallelStream() { return StreamSupport.stream(spliterator(), true); }
:返回一个并行流,并行流背后可能有多个线程并行执行,使用并行流不需要显示管理线程
2. 使用 Stream API 重写下面代码
Demo1
1
2
3
4
5
6
7
8
9
10//传统代码
List<Student> above90List = new ArrayList<> ();
for(Student t : students) {
if(t.getScore() > 90) {
above90List.add(t);
}
}
//使用 Stream API
List<Student> above90List = students.stream().filter(t -> t.getScore() > 90).collect(Collectors.toList());Demo2
1
2
3
4
5
6
7
8//传统代码
List<String> nameList = new ArrayList<> (students.size());
for(Student t : students) {
nameList.add(t.getName());
}
//使用 Stream API
List<String> nameList = students.stream().map(Student::getName).collect(Collectors.toList());Demo3
1
2
3
4
5
6
7
8
9
10//传统代码
List<String> nameList = new ArrayList<> ();
for(Student t : students) {
if(t.getScore() > 90) {
nameList.add(t.getName());
}
}
//使用 Stream API
List<String> above90Names = students.stream().filter(t -> t.getScore() > 90).map(Student::getName).collect(Collectors.toList());总结
- 代码直观易读,
filter()
和map()
都需要对流中的每个元素操作一次,一起使用并不会遍历两次,性能没有问题 - 实际上,调用
filter()
和map()
都不会执行任何实际的操作,它们只是在构建操作的流水线 - 调用
collect()
才会触发实际的遍历执行,在一次遍历中完成过滤、转换以及收集结果的任务
- 代码直观易读,
3. 函数式编程中什么是中间操作、什么是终端操作
- 中间操作:像
filter()
、map()
这种不实际触发执行、用于构建流水线、返回Stream
的操作 - 终端操作:像
collect()
这种触发实际执行、返回具体结果的操作
4. 函数式数据处理的概念和特点是什么
- 概念:利用 Stream API 基本函数、声明式实现集合数据处理功能的组合式或者说链式的编程风格
- 特点
- 没有显示的循环迭代,循环过程被
Stream
的方法隐藏了 - 提供了声明式的处理函数,比如
filter
,它封装了数据过滤的功能,而传统代码是命令式的,需要一步步的操作指令 - 流畅式接口,方法调用链接在一起,清晰易读
- 没有显示的循环迭代,循环过程被
5. Stream 流中的中间操作有哪些
filter
、map
、distinct
、sorted
、skip
、limit
、peek
mapToLong
、mapToInt
、mapToDouble
和flatMap
等
6. 使用 distinct
返回字符串列表中长度小于 3 的字符串、转换为小写、只保留唯一的
1 | List<String> list = Arrays.asList(new String[] {"abc", "def", "hello", "Abc"}); |
7. 怎样理解 distinct
操作符
distinct
返回一个新的Stream
,过滤重复的元素,只留下唯一的元素,是否重复是根据equals()
方法来比较的,可以与其他函数(比如filter
、map
)结合使用虽然都是中间操作,但
distinct
和filter
、map
是不同的filter
和map
都是无状态的,对于流中的每一个元素,处理都是独立的,处理后即交给流水线中的下一个操作distinct
是有状态的,在处理过程中,它需要在内部纪录之前出现过的元素,如果已经出现过,即重复元素,它就会过滤掉,不传递给流水线中的下一个操作
对于顺序流,内部实现时,
distinct
操作会使用HashSet
纪录出现过的元素;如果流是有顺序的,需要保留顺序,会使用LinkedHashSet
8. 使用 sorted
过滤得到 90 分以上的学生,然后按分数从高到低排序,分数一样的按名称排序
1 | //有两个 sorted() 方法:Stream<T> sorted() 和 Stream<T> sorted(Comparator<? super T> comparator) |
9. 使用 skip
、limit
将学生列表按照分数排序,返回第 3 名到第 5 名
1 | //它们的定义:Stream<T> skip(long n) 和 Stream<T> limit(long maxSize) |
10. 怎样理解 peek
操作符
peek
的定义为:Stream<T> peek(Consumer<? super T> action)
- 含义:
peek
返回的流与之前的流是一样的,没有变化,但它提供了一个Consumer
,会将流中的每一个元素传给该Consumer
。这个方法的主要目的是支持调试,可以使用该方法观察在流水线中流转的元素 - Demo:
List<String> above90Names = students.stream().filter(t -> t.getScore() > 90).peek(System.out::println).map(Student::getName).collect(Collectors.toList())
11. 怎样理解 mapToLong
、mapToInt
、mapToDouble
操作符
map
函数接受的参数是一个Function<T, R>
,为避免装修/拆箱,提高性能,Stream
还有如下返回基本类型特定的流的方法DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper)
IntStream mapToInt(ToIntFunction<? super T> mapper)
LongStream mapToLong(ToLongFunction<? super T> mapper)
DoubleStream
、IntStream
、LongStream
是基本类型特定的流,有一些专门的更为高效的方法。比如,求学生列表的分数总和:double sum = students.stream().mapToDouble(Student::getScore).sum()
12. 怎样理解 flatMap
操作符
flatMap
的定义为:<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)
flatMap
接受一个函数mapper
,对流中的每一个元素,mapper
会将该元素转换为一个流Stream
,然后把新生成流的每一个元素传递给下一个操作。比如1
2
3List<String> lines = Arrays.asList(new String[] {"hello abc", "老隋 编程"});
List<String> words = lines.stream().flatMap(line -> Arrays.stream(line.split("\\s+"))).collect(Collectors.toList());
System.out.println(words);这里的
mapper
将一行字符串按空白符分隔为了一个单词流,Arrays.stream
可以将一个数组转换为一个stream
流,输出为:[hello, abc, 老隋, 编程]
实际上,
flatMap
完成了一个 1 到 n 的映射
13. Stream
流中的终端操作有哪些
collect
、max
、min
、count
、allMatch
、anyMatch
noneMatch
、findFirst
、findAny
、forEach
、toArray
和reduce
等
14. 怎么理解 max/mix
max/mix
的定义Optional<T> max(Comparator<? super T> comparator)
:返回流中的最大值Optional<T> min(Comparator<? super T> comparator)
:返回流中的最小值
java.util.Optional
是 Java 8 引入的一个新类,它是一个泛型容器类,内部只有一个类型为T
的单一变量value
,可能为null
,也可能不为null
Optional
的作用是:用于准确地传递程序的语义,它清楚地表明,其代表的值可能为null
,程序员应该进行适当的处理在
max/min
的例子中,通过声明返回值为Optional
,我们可以知道具体的返回值不一定存在,这发生在流中不含任何元素的情况下举例,返回分数最高的学生(假定
students
不为空):Student student = students.stream().max(Comparator.comparing(Student::getScore).reversed()).get();
15. 怎么理解 count
- 返回流中元素的个数
- 举例,统计大于 90 分的学生个数:
long above90Count = students.stream().filter(t -> t.getScore() > 90).count();
16. 怎么理解 allMatch
、anyMatch
、noneMatch
这几个函数都接受一个谓词
Predicate
,返回一个boolean
值,用于判定流中元素是否满足一定的条件。它们的区别是allMatch
:只有在流中所有元素都满足的条件下才返回true
anyMatch
:只要流中有一个元素满足条件就返回true
noneMatch
:只有流中所有元素都不满足条件才返回true
如果流为空,那么这几个函数的返回值都是
true
都是短路操作,不一定需要处理所有元素就能得出结果
举例,判断是不是所有学生都及格了(不小于 60 分):
boolean allPass = students.stream().allMatch(t -> t.getScore() >= 60);
17. 怎么理解 findFirst
、findAny
它们的定义为
Optional<T> findFirst()
:返回第一个元素Optional<T> findAny()
:返回任一元素
它们的返回类型都是
Optional
,如果流为空,返回Optional.empty()
,都是短路操作举例,随便找一个不及格的学生:
Optional<Student> student = students.stream().filter(t -> t.getScore() < 60).findAny();
18. 怎么理解 forEach
void forEach(Consumer<? super T> action)
:在并行流中,不保证处理的顺序void forEachOrdered(Consumer<? super T> action)
:会保证按照流中元素的出现顺序进行处理- 它们都接受一个
Consumer
,对流中的每一个元素,传递元素给Consumer
- 举例,逐行打印大于 90 分的学生:
students.stream().filter(t -> t.getScore() > 90).forEach(System.out::println);
19. 怎么理解 toArray
toArray
将流转换为数组Object[] toArray()
<A> A[] toArray(IntFunction<A[]> generetor)
IntFunction
的定义为:public interface IntFunction<R> { R apply(int value); }
举例,获取 90 以上的学生数组:
Student[] above90Arr = students.stream().filter(t -> t.getScore() > 90).toArray(Student[]::new);
20. 怎么理解 reduce
reduce
代表归约或者叫折叠,它是max
、min
、count
的更为通用的函数,将流中的元素归约为一个值。有三个reduce
函数Optional<T> reduce(BinaryOperator<T> accumulator)
T reduce(T identity, BinaryOperator<T> accumulator)
<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner)
第二个
reduce
函数多了一个identity
参数,表示初始值第一个和第二个
reduce
函数的返回类型只能是流中元素的类型,而第三个reduce
函数更为通用,它的归约类型可以自定义。另外,它多了一个combiner
参数,combiner
用在并行流中,用于合并子线程的结果reduce
函数虽然更为通用,但比较费解,难以使用,一般情况下应该优先使用其他函数collect
函数比reduce
函数更为通用、强大和易用
21. 函数式编程中构建流的方式有哪些
Collection
接口的stream
、parallelStream
获取流parallelStream
并行流内部会使用多线程,线程个数一般与系统的 CPU 核数一样,以充分利用 CPU 的计算能力并行流内部会使用 Java 7 引入的 fork/join 框架,即处理由 fork 和 join 两个阶段组成
- fork 就是将要处理的数据拆分为小块,多线程按小块进行并行计算
- join 就是将小块的计算结果进行合并
使用并行流,不需要任何线程管理的代码,就能实现并行
Arrays
有一些stream
方法,可以将数组或子数组转换为流Stream
也有一些静态方法,可以构建流
22. 函数式数据处理思维是怎样的
流定义了很多数据处理的基本函数
- 对于一个具体的数据处理问题,解决的主要思路就是组合利用这些基本函数,以声明式的方式简洁地实现期望的功能
- 这种思路就是函数式数据处理思维,相比直接利用容器类 API 的命令式思维,思考的层次更高,因为封装的层次更高
Stream API 也与各种基于 Unix 系统的管道命令类似