本人参考这篇文章(传送门》》》)学习了java 8 Stream API的使用,以下内容为一些笔记整理。
java8 中的 Stream API 使用详解
简介
Stream 是java 8的一大亮点,第一眼会把它当成IO 流,其实不然。Java 8 Stream是对集合对象(Collection)的功能增强,为集合对象提供非常便利的聚合操作,或者大批量数据操作。Stream API也采用了Lambda表达式写法,大大提高了编程效率和程序可读性。
Stream 概览
对Stream的认识
它不是集合元素,不是数据结构,可以把它理解为一个高级版本的Iterator。
Iterator -> 显式的一个个遍历元素,并附加一些操作。
Stream -> 首先给出需要执行的操作,然后Stream会隐式地在内部进行遍历,进行数据操作。
Stream 可以并行化操作,当使用并行去处理时,数据会被分成多个段,每个段在不同的线程中进行处理,然后结果一并输出。Stream 的并行操作依赖于Java 7的Fork/Join 框架来拆分任务和加速处理过程。
Java 并行API演变历程:
1. 1.0-1.4 中的 java.lang.Thread
2. 5.0 中的 java.util.concurrent
3. 6.0 中的 Phasers
等
4. 7.0 中的 Fork/Join
框架
5. 8.0 中的 Lambda
流的操作类型
流的操作类型分为两种:
- Intermediate : 打开流、进行相关操作、返回一个新的流给到下个操作使用。这类操作是惰性的(Lazy),仅仅是调用了此方法,底层并未开始真正的遍历。
- Terminal : 一个流只能有一个Terminal操作,当这个操作执行后,流就被“用光了”,无法在操作,所以这必然是流最后一个操作。Terminal操作时,底层才真正开始流的遍历,并返回一个结果,或者一个side effect。
- 还有一个操作被称作 short-circuiting . 当操作一个无限大的 Stream , 而又希望在有限时间内完成操作,则在管道内拥有一个 short-circuiting 操作是必要非充分条件。
int sum = peopleList.stream()
.filter(p -> p.getSex() == GIRL)
.mapToInt(p -> p.getAge())
.sum();
/** 这段代码的意思:
* 求人物列表中所有女孩的年龄之和:
* 构造人stream -> 过滤出女孩 -> 转换成年龄stream -> 求和
*
* 1. peopleList.stream()通过List构造一个流
* 2. filter() 过滤操作、mapToInt() 转换操作 为 Intermediate 操作类型
* 3. sum() 求和操作 为 Terminal 操作类型
* 4. 当执行sum()时,底层才真正开始遍历,进行过滤、转换等操作
*/
流的使用详解
流的构造与转换
构造流的常用方法
// 1. 使用初始值
Stream stream = Stream.of(1, 2, 3);
// 2. 使用数组
Int[] numbers = new Int[]{1, 2, 3};
stream = Stream.of(numbers);
stream = Arrays.stream(numbers);
// 3. 使用集合
List<Integer> list = Arrays.asList(numbers);
stream = list.stream();
流转换为其它数据结构
// 1. to 数据类型
Stream stream = Stream.of("ni","hao","ya");
String[] strs = stream.toArray();
// 2. to 集合
List<String> list = stream.collect(Collectors.toList());
Set set = stream.collect(Collectors.toSet());
// 3. to 字符串
String str = stream.collect(Collectors.joining()).toString();
流的操作
常见操作归类:
- Intermediate :
map(map、 flatMap、 mapToInt等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered - Terminal :
forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator - Short-circuiting :
anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit
map 与 flatMap 的使用
先来看看map。
// 举个栗子:整数list -> 平方数list
List<Integer> nums = Arrays.asList(1, 2, 3, 4);
List<Integer> squareNums = nums.stream().
map(n -> n * n).
collect(Collectors.toList());
map 生成 1:1 映射,将每个输入元素,按照规则转换成另一个元素。
再来看看flatMap。
以下例子引用别人的讲解:传送门》》》》》
案例:对给定单词列表 [“Hello”,”World”],你想返回列表[“H”,”e”,”l”,”o”,”W”,”r”,”d”]
// 1. 使用map
String[] words = new String[]{"Hello","World"};
List<String[]> a = Arrays.stream(words)
.map(word -> word.split(""))
.distinct()
.collect(toList());
a.forEach(System.out::print);
// 2. 使用flatMap
String[] words = new String[]{"Hello","World"};
List<String> a = Arrays.stream(words)
.map(word -> word.split(""))
.flatMap(Arrays::stream)
.distinct()
.collect(toList());
a.forEach(System.out::print);
filter
对 stream 中的元素按一定条件进行过滤,剩余元素生成一个新的stream。
// 挑出正数
int[] numbers = new Int[]{-1,2,0,3,-4,-5};
int[] pNumbers =
Arrays.stream(numbers)
.filter(n -> n > 0)
.toArray();
forEach
forEach 方法接收一个lambda表达式,然后在stream的每个元素上执行该表达式。
// 1. 打印集合
int[] numbers = new Int[]{-1,2,0,3,-4,-5};
Arrays.stream(numbers)
.forEach(System.out::println);
// 2. 每个元素 +1
int[] numbers = new Int[]{-1,2,0,3,-4,-5};
Arrays.stream(numbers)
.forEach(n -> n=n+1);
注意:forEach 是 Terminal 操作,它执行过后,stream 就已经被“用光了”,无法再进行操作。
在 Intermediate 操作中,peek也具有相似的功能。
findFirst 方法 及 Optional 类型
这是一个 termimal 兼 short-circuiting 操作,它总是返回 Stream 的第一个元素,或者空。
它的返回值类型:Optional。作为一个容器,它可能含有某值,或者不包含。使用它的目的是尽可能避免 NullPointerException。
// Optional 两个用例
String strA = " abcd ", strB = null;
print(strA);
print("");
print(strB);
getLength(strA);
getLength("");
getLength(strB);
public static void print(String text) {
// Java 8
Optional.ofNullable(text).ifPresent(System.out::println);
// Pre-Java 8
if (text != null) {
System.out.println(text);
}
}
public static int getLength(String text) {
// Java 8
return Optional.ofNullable(text).map(String::length).orElse(-1);
// Pre-Java 8
// return if (text != null) ? text.length() : -1;
};
Stream 中的 findAny、max/min、reduce 等方法等返回 Optional 值。还有例如 IntStream.average() 返回 OptionalDouble 等等。
reduce
将 stream 元素组合起来。它提供一个起始值,和stream的第1,第2…第n个元素进行组合。从这个意义上说,字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce。
// 1. 字符串连接 contact="ABCD"
String contact = Stream.of("A","B","C","D")
.reduce("",String::concat);
// 2. 求最小值 minValue=-3.0
double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0)
.reduce(Double.MAX_VALUE,Double::min);
// 3. 求和 sumValue = 10 , 有起始值
int sumValue = Stream.of(1, 2, 3, 4)
.reduce(0,Integer::sum);
// 4. 求和 sumValue = 10 , 无起始值
// 无起始值的reduce,由于stream可能没有元素,所以返回的是 Optional。
int sumValue = Stream.of(1, 2, 3, 4)
.reduce(Integer::sum)
.get();
limit & skip
limit 返回 Stream 的前面 n 个元素;skip 则是扔掉前 n 个元素(它是由一个叫 subStream 的方法改名而来)。
sorted
对 stream 进行排序。它比数组的排序更强之处在于你可以首先对 Stream 进行各类 map、filter、limit、skip 甚至 distinct 来减少元素数量后,再排序,这能帮助程序明显缩短执行时间。
Integer[] numbers = new Integer[]{5,1,4, 2, 3};
Arrays.stream(numbers)
.sorted((a, b) -> b-a)
.forEach(System.out::println);
output:
5
4
3
2
1
min & max & distinct
// 1. 找出最长一行的长度
BufferedReader br = new BufferedReader(new FileReader("c:\\SUService.log"));
int longest = br.lines().
mapToInt(String::length).
max().
getAsInt();
br.close();
System.out.println(longest);
// 2. 找出全文的单词,转小写,并排序
List<String> words = br.lines().
flatMap(line -> Stream.of(line.split(" "))).
filter(word -> word.length() > 0).
map(String::toLowerCase).
distinct().
sorted().
collect(Collectors.toList());
br.close();
System.out.println(words);
Match
Stream 有三个 match 方法:
- allMatch : stream 全部元素符合传入的predicate,返回true
- anyMatch : Stream 中只要有一个元素符合传入的 predicate,返回 true
- noneMatch : Stream 中没有一个元素符合传入的 predicate,返回 true
List<Person> persons = new ArrayList();
persons.add(new Person(1, "name" + 1, 10));
persons.add(new Person(2, "name" + 2, 21));
persons.add(new Person(3, "name" + 3, 34));
persons.add(new Person(4, "name" + 4, 6));
persons.add(new Person(5, "name" + 5, 55));
boolean isAllAdult = persons.stream().
allMatch(p -> p.getAge() > 18);
System.out.println("All are adult? " + isAllAdult);
boolean isThereAnyChild = persons.stream().
anyMatch(p -> p.getAge() < 12);
System.out.println("Any child? " + isThereAnyChild);
output:
All are adult? false
Any child? true
进阶
自己生成流
Stream.generate
// 1. 生成10个随机数
Random seed = new Random();
Supplier<Integer> random = seed::nextInt;
Stream.generate(random).limit(10).forEach(System.out::println);
// 2. 自实现 Supplier
Stream.generate(new PersonSupplier())
.limit(10)
.forEach(p -> System.out.println(p.getName() + ", " + p.getAge()));
private class PersonSupplier implements Supplier<Person> {
private int index = 0;
private Random random = new Random();
@Override
public Person get() {
return new Person(index++, "StormTestUser" + index, random.nextInt(100));
}
}
Stream.iterator
iterate 跟 reduce 操作很像
生成的流 : 1 2 3 4 5
Stream.iterator(1, n -> n+1).limit(5)
Collectors类
java.util.stream.Collectors 类的主要作用就是辅助进行各类有用的 reduction 操作,例如转变输出为 Collection,把 Stream 元素进行归组。
groupingBy/partitioningBy
Map<Integer, List<Person>> personGroups = Stream.generate(new PersonSupplier())
.limit(100)
.collect(Collectors.groupingBy(Person::getAge));
Iterator it = personGroups.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<Integer, List<Person>> persons = (Map.Entry) it.next();
System.out.println("Age " + persons.getKey() + " = " + persons.getValue().size());
}
// 首先生成 100 人的信息,然后按照年龄归组,相同年龄的人放到同一个 list 中
output :
Age 0 = 2
Age 1 = 2
Age 5 = 2
Age 8 = 1
Age 9 = 1
Age 11 = 2
……