Stream流
- Stream流是一个来自数据源的元素队列并具有聚合操作的
- Stream流包含了如下三个部分
- 元素序列:一个流对外提供一个接口,可以访问到一串特定的数据。流本身是不存储元素的,但可以根据需要进行计算转化
- 数据源:数据来源,如数据结构,数组,文件等
- 聚合操作:流支持像SQL操作或者其他函数式语言的操作,如filter / map / reduce / find / match / sorted等
public static void main(String[] args) {
String[] planets = {
"Mercury", "Venus", "Earth", "Mars",
"Jupiter", "Saturn", "Uranus", "Neptune"
};
// 数组转化成List
List<String> list = Arrays.asList(planets);
// 采用流的方式里处理数组中长度大于5的元素个数
Long count = list.stream().filter((planet) -> planet.length() > 5).count();
System.out.println("统计结果: " + count);
// 采用并行流
Long countParall = list.parallelStream().filter((planet) -> planet.length() > 5).count();
System.out.println("并行流处理结果: " + countParall);
}
Stream流的特点
- 很多流操作也是返回一个流。
- 流操作进行迭代,用户感知不到循环遍历。
流的工作流程
- 创建一个流
- 指定将流转化为其他流的中间操作,可包括多个步骤(惰性操作)
- 应用终止操作,从而产生结果。这个操作会强制执行之前的惰性操作。这个步骤以后,流就再也不用了
- 注意:一个流,一次只能一个用途,不能多个用途,用了不能再用了
Stream流的创建
使用Collection接口的stream方法
- 还有其他的子类,如LinkedList , LinkedSet , TreeSet , Vector等
public static void main(String[] args) throws IOException {
Stream<String> as = new ArrayList<String>().stream();
Stream<String> hs = new HashSet<String>().stream();
Stream<String> b1 = Arrays.stream("a,b,c,e".split(","), 3, 5);
}
使用Stream类进行转化
- of方法,直接将数组转化
Stream<Integer> c1 = Stream.of(new Integer[5]);
Stream<String> c2 = Stream.of("a,b,c".split(","));
Stream<String> c3 = Stream.of("a", "b", "c");
- empty方法,产生一个空流
Stream<String> d1 = Stream.empty();
使用generate方法
- 接收一个Lambda表达式或者一个方法引用
Stream<String> e1 = Stream.generate(() -> "hello");
Stream<Double> e2 = Stream.generate(Math::random);
使用iterrate方法
Stream<BigInteger> e3 = Stream.iterate(BigInteger.ZERO, n -> n.add(BigInteger.ONE));
基本类型流 (只有三种)
- IntStream, LongStream, DoubleStream
IntStream s1 = IntStream.of(1, 2, 3, 4);
IntStream s2 = Arrays.stream(new int[] {1, 2, 3, 4});
IntStream s3 = IntStream.generate(() -> (int) (Math.random() * 100));
// range(startNumber, length); 默认步长为1,不包括结束
IntStream s4 = IntStream.range(1, 5);
// 包括结束
IntStream s5 = IntStream.rangeClosed(1, 5);
// Stream->基本对象流
IntStream s6 = IntStream.of(1, 2, 3);
Stream<Integer> s7 = s6.boxed();
IntStream s8 = s7.mapToInt(Integer::intValue);
并行流
- 使得所有中间转换操作都将被并行化
- Collections.parallelStream()将任何集合转为并行流
- Stream.parallel()方法,产生一个并行流
IntStream s1 = IntStream.range(1, 10);
long evenNum = s1.paralled().filter(n -> n % 2 ==0).count();
注意:需要保证传给并行流的操作不存在竞争
Files.lines方法创建流
Stream<String> contents = Files.lines(Paths.get("C:/hello.txt"));
Patten的splitAsStream方法创建流
Stream<String> works = Pattern.compile(",").splitAsStream("a,b,c");
Stream流的转换
- Stream流的转化是从一种流到另外一种流
- 过滤(去重)、排序、转化、抽取 / 跳过 / 连接
过滤filter
- 接收一个Lambda表达式,对每个元素进行判定,符合条件的留下
Stream<Integer> s1 = Stream.of(1, 2, 3, 4, 5);
Stream<Integer> s2 = s1.filter(n -> n > 2);
s2.forEach(System.out::println); // 结果为3 4 5
去重distinct
- 对流的元素进行过滤、去除重复,只留下不重复元素
- 对象的判定,先调用hashCode方法,再调用equals方法
Stream<Integer> s1 = Stream.of(1, 2, 3, 3, 2, 1);
Stream<Integer> s2 = s1.distinct();
s2.forEach(System.out::println); // 结果为1 2 3
ArrayList<Student> students = new ArrayList<Student>();
students.add(new Student("Tom", 20));
students.add(new Student("Tom", 20));
students.add(new Student("Jerry", 20));
students.add(new Student("Jerry", 18));
// 先对象的hasCode再调用equals方法进行判断
// hashCode和equals则判定为两个对象相同
Stream<Student> s3 = students.stream().distinct();
s3.forEach(System.out::println);
排序sorted
- 对流的基本类型包装类元素进行排序
- 提供Comparator,对流的元素进行排序
- 对流的自定义对象元素进行排序,调用对象的compareTo方法
Stream<Integer> s1 = Stream.of(3, 2, 4, 1, 5);
// 默认升序
Stream<Integer> s2 = s1.sorted();
s2.forEach(System.out.println); // 结果: 1 2 3 4 5
String[] planets = new String[] {
"Mercury", "Venus", "Earth",
"Mars", "Jupiter", "Saturn",
"Uranus", "Neptune"
};
// 对字符串的长度进行排序,默认升序
Stream<String> s3 = Stream.of(planets).sorted(
Comparator.Comparing(String::length)
);
s3.forEach(System.out::println);
public class Cat implements Comparable<Cat> {
private int size;
public Cat(int size) {
this.size = size;
}
public int compareTo(Cat cat) {
Cat c = new Cat(5);
return this.size - c.size;
}
public String toString() {
return "Size: " + size;
}
}
public static void main(String[] args) {
Stream<Cat> cats = Stream.of(new Cat[] {});
// 自定义排序规则调用compareTo方法
Stream<Cat> s4 = cats.sorted();
s4.forEach(System.out::println);
}
流的转化
- 转为map
- 利用方法引用对流每个元素进行函数计算
Stream<Double> s1 = Stream.of(-1.5, 2.5, -3.5);
Stream<Double> s2 = s1.map(Math::abs);
s2.forEach(System.out::println);
- 利用Lambda表达式对流每个元素进行函数计算
Stream<Integer> s1 = Stream.of(1, 2, 3, 4);
Stream<Integer> s2 = s1.map(n -> n * n);
s2.forEach(System.out::println); // 输出 1 4 9 16
public static void main(String[] args) {
String[] planets = new String[] { "Mercury", "Venus", "Earth" };
Stream<Stream<String>> allLetters = Stream.of(planets).map(word -> letters(word));
// 遍历输出
allLetters.forEach((words) -> words.forEach((word) -> {
System.out.print(word + " ");
}));
}
public static Stream<String> letters(String word) {
List<String> result = new ArrayList<>();
for (int i = 0; i < word.length(); i++) {
result.add(word.substring(i, i+1));
}
return result.stream();
}
运行结果:
M e r c u r y V e n u s E a r t h
抽取limit
- 限定了流的元素个数
Stream<Integer> s1 = Stream.of(1, 2, 3, 4, 5);
Stream<Integer> s2 = s1.limit(3);
s2.forEach(System.out::print); // 结果为123
跳过skip
- 跳过前面n个元素
Stream<Integer> s1 = Stream.of(1, 2, 3, 4, 5);
Stream<Integer> s2 = s1.skip(3);
s2.forEach(System.out::print); // 结果为45
连接concat
public static void main(String[] args) {
Stream<String> s5 = Stream.concat(letters("hello"), letters("world"));
s5.forEach(System.out::print);
}
public static Stream<String> letters(String word) {
List<String> result = new ArrayList<>();
for (int i = 0; i < word.length(); i++) {
result.add(word.substring(i, i+1));
}
return result.stream();
}
调试peek
- s1.forEach输出时会额外的执行peek中的函数功能
public static void main(String[] args) {
Stream<Double> s1 = Stream.iterate(1.0, (n) -> n * 2)
.peek((n) -> System.out.println("number: " + n)).limit(5);
s1.forEach(System.out::println);
}
运行结果:
number: 1.0
1.0
number: 2.0
2.0
number: 4.0
4.0
number: 8.0
8.0
number: 16.0
16.0
Optional类型
- Optional<T>类型
- Optional<T>是一个包装器对象
- 要么包装了类型T对象,要么没有包装任何对象(还是null)
- 如果T有值,那么直接返回T的对象,如果T是null,那么可以返回一个替代物
- Optional<T>类型可以解决空指针异常问题
public static void main(String[] args) {
// Optaional类型的基本使用
Optional<String> s1 = Optional.of(new String("abc"));
// 获取Optional中的值
String s2 = s1.get();
System.out.println("s2: " + s2);
Optional<String> s3 = Optional.empty();
// 如果s3里没有值就会使用s3.orElse方法的字符串作为替代物
// 如果s3有值则返回原来的值
String s4 = s3.orElse("Optional is empty");
System.out.println("s4: " + s4);
}
运行结果:
s2: abc
s4: Optional is empty
Optional的创建
- of方法
- empty方法
- ofNullable方法,对于对象有可能是null情况下,安全创建
public static void main(String[] args) {
Optional<String> str = Optional.of(new String("abc"));
Optional<String> str1 = Optional.empty();
String str2 = null;
// str2不为null,str3就是str2,否则str3就为Optional.empty()
Optional<String> str3 = Optional.ofNullable(str2);
System.out.println(str3);
}
Optional类型的使用
- get方法,获取值,不安全的用法
- 注意:直接使用get,很容易引发NoSuchElementException异常
- orElse方法,获取值,如果为null,采用替代物的值
- orElseGet方法,获取值,如果为null,采用Lambda表达式值返回
- orElseThrow方法,获取值,如果为null,抛出异常
- ifPresent方法,判断是否为null,不为null返回true
- isPresent (Consumer),判断是否为null,如果不为null,则进行后续的Consumer操作,如果为null,则不做任何操作
- 使用isPresent判断值是否存在和判断null一样低效
- map (Function),将值传递给Function函数还进行计算。如果为null,则不计算
流的计算
流的计算类型
- 聚合函数: count / max / min / …
- 自定义约简: reduce
- 查看 / 遍历元素:iterator / forEach
- 存放到数据结构中
聚合函数
- count(),计数
- max(Comparator),最大值,需要比较器
- min(Comparator),最小值,需要比较器
- findFirst(),找到第一个元素
- findAny(),找到任意一个元素
- anyMatch(Predicate),如有任意一个元素满足Predicate,返回true
- allMatch(Predicate),如所有元素满足Predicate,返回true
- noneMatch(Predicate),如没有任何元素满足Predicate,返回true
自定义约简
- reduce,传递一个二元函数BinaryOperator,对流元素进行计算
- 如求和、求积、字符串连接等
public static void main(String[] args) {
Integer[] a = new Integer[] { };
Stream<Integer> s1 = Stream.of(a);
// 数组求和
Optional<Integer> sum = s1.reduce(Integer::sum);
System.out.println(sum.orElse(-1));
Stream<Integer> s2 = Stream.of(a);
// 数组乘积
Optional<Integer> product = s2.reduce((x, y) -> x * y);
System.out.println(product.orElse(-1));
Stream<Integer> s3 = Stream.of(a);
// 默认值为1,当a为空数组是输出1
Integer product2 = s3.reduce(1, (x, y) -> x * y);
System.out.println(product2);
}
查看 / 遍历元素
- iterator,遍历元素
- forEach(Consumer),应用一个函数到每个元素上
public static void main(String[] args) {
Integer[] a = new Integer[] { 2, 4, 6, 8 };
Stream<Integer> s1 = Stream.of(a);
Iterator<Integer> it1 = s1.filter(number -> number > 2).iterator();
while (it1.hasNext()) {
System.out.print(it1.next() + " ");
}
System.out.println();
Stream<Integer> s2 = Stream.of(a);
s2.filter(numbers -> numbers > 2).forEach((number) -> System.out.print(number + " "));
}
存储到数据结构中
- toArray(),将结果转化成数组
- collect(Collectors.toList()),将结果转化成List
- collect(Collectors.toSet()),将结果转化成Set
- collect(Collectors.toMap()),将结果转化成Map
- collect(Collectors.joining()),将结果连接起来
流的高阶计算
- 分组groupingBy和分区partitionBy
- 分组后的约简 (相当于SQL中的having)
- counting
- summing
- maxBy
- minBy
流的注意事现
- 一个流只能使用一次
- 避免创建无限流
- 注意操作顺序
无限流
// 无限流
IntStream.iterate(0, i -> i + 1).forEach(System.out::println);
// 无限流
IntStream.iterate(0, i -> (i + 1) % 2).distinct().limit(10).forEach(System.out::println);
操作顺序的不同结果不同
// 先limit后skip
IntStream.iteracte(0, i -> i + 1);
.limit(10)
.skip(5)
.forEach(System.out::println); // 结果为5-9
// 先skip后limit
IntStream.iteracte(0, i -> i + 1)
.skip(5)
.limit(10)
.forEach(System.out::println) // 结果为5-14
并行流的使用建议
- 由于并行流底层是使用Fork-Join Pool,处理计算密集型任务,所以数据量过小不用
- 数据结构不容易分解的时候不用,如LinkedList等
- 数据频繁拆箱装箱不用
- 涉及到findFirst或者limit的时候不用