一、概述
Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。
Stream(流)是一个来自数据源的元素队列并支持聚合操作
- 元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
- 数据源 流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。
- 聚合操作 类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。
和以前的Collection操作不同, Stream操作还有两个基础的特征:
- Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
- 内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。
二、Stream流操作步骤概述
-
创建Stream:一个数据源(如:集合、数组),获取一个流;
-
进行中间操作:一个中间操作链,对数据源的数据进行处理;
-
进行终止操作:一个终止操作,执行中间操作链,并产生结果。
三、Stream流的常见创建方法
- 通过Collection系列集合提供的顺序流stream()或并行流parallelStream()
List<Integer> list = new ArrayList<>();
// 创建一个顺序流
Stream<Integer> stream = list.stream();
// 创建一个并行流
Stream<Integer> parallelStream = list.parallelStream();
- 通过Arrays中的静态方法stream()获取数据流
Integer[] intArr = new Integer[2];
Stream<Integer> stream = Arrays.stream(intArr);
- 使用Stream中的静态方法:of()、iterate()、generate()
// of方法
Stream<Integer> of = Stream.of(1, 2, 3, 4, 5);
// iterate方法,第一个参数seed种子(初始值),第二个参数定义数据的生成规则
Stream<Integer> limit = Stream.iterate(1, (x) -> {
return x + 1;
}).limit(5);
// 遍历
limit.forEach((x) -> {
System.out.println(x);
});
// generate方法
Stream<Double> stream = Stream.generate(Math::random).limit(5);
// 遍历
stream.forEach(System.out::println);
- 使用 BufferedReader.lines() 方法,将每行内容转成流
BufferedReader reader = new BufferedReader(new FileReader("D:\\test.txt"));
Stream<String> lineStream = reader.lines();
5.使用 Pattern.splitAsStream() 方法,将字符串分隔成流
Pattern pattern = Pattern.compile(",");
Stream<String> splitAsStream = pattern.splitAsStream("a,b,c,d");
splitAsStream.forEach(System.out::println);
四、Stream流的常见中间操作
- 筛选与切片
filter:过滤流中的某些元素
Stream<Integer> stream = Stream.of(2, 2, 3, 7, 8);
// 过滤出来偶数
Stream<Integer> filter = stream.filter((x) -> {
return (x & 1) == 0;
});
filter.forEach((x) -> {
System.out.print(x + "\t");
});
// 2 2 8
limit(n):获取n个元素,n小于0时抛出异常,n大于stream 中的元素个数时,获取全部元素
Stream<Integer> stream = Stream.of(2, 2, 3, 7, 8);
// 取前3个元素
Stream<Integer> stream2= stream.limit(3);
stream2.forEach((x) -> {
System.out.print(x + "\t");
});
// 2 2 3
skip(n):跳过n元素,配合limit(n)可实现分页,n小于0时,抛出异常,n等于0时,获取所有元素,n大于stream 中的元素个数时,获取为空。
Stream<Integer> stream = Stream.of(2, 2, 3, 7, 8);
// 跳过前3个元素
Stream<Integer> stream2 = stream.skip(3);
stream2.forEach((x) -> {
System.out.print(x + "\t");
});
// 7 8
distinct:通过流中元素的 hashCode() 和 equals() 去除重复元素
Stream<Integer> stream = Stream.of(2, 2, 3, 7, 8);
// 去重
Stream<Integer> stream2 = stream.distinct();
stream2.forEach((x) -> {
System.out.print(x + "\t");
});
//2 3 7 8
- 映射
map:接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
Stream<Integer> stream = Stream.of(2, 2, 3, 7, 8);
// 将所有元素通过map映射处理成double类型
Stream<Double> stream2 = stream.map((x) -> {
return x * 1.0;
});
stream2.forEach((x) -> {
System.out.print(x + "\t");
});
// 2.0 2.0 3.0 7.0 8.0
flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。
Stream<Integer> stream = Stream.of(2, 2, 3, 7, 8);
// 将所有元素通过map映射处理成double类型
Stream<Double> stream2 = stream.flatMap((x) -> {
// 处理成stream流
Stream<Double> s = Stream.of(x * 1.0);
return s;
});
stream2.forEach((x) -> {
System.out.print(x + "\t");
});
// 2.0 2.0 3.0 7.0 8.0
- 排序
sorted():自然排序,流中元素需实现Comparable接口
Stream<Integer> stream = Stream.of(2, 1, 7, 3, 8);
// 将元素从小到大排序
Stream<Integer> stream2 = stream.sorted();
stream2.forEach((x) -> {
System.out.print(x + "\t");
});
// 1 2 3 7 8
sorted(Comparator com):定制排序,自定义Comparator排序器
Stream<Integer> stream = Stream.of(2, 1, 7, 3, 8);
// 自定义排序方式,将元素从大到小排序
Stream<Integer> stream2 = stream.sorted((a, b) -> {
return Math.negateExact(a - b);
});
stream2.forEach((x) -> {
System.out.print(x + "\t");
});
// 8 7 3 2 1
- 消费
peek:如同于map,能得到流中的每一个元素。但map接收的是一个Function表达式,有返回值;而peek接收的是Consumer表达式,没有返回值。
static class User {
public int id;
public String name;
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + "]";
}
}
public static void main(String[] args) {
Stream<User> stream = Arrays.asList(new User[] {new User(), new User()}).stream();
// 使用peek初始化stream中的元素
Stream<User> stream2 = stream.peek((x) -> {
x.id = new Random().nextInt(10);
x.name = "name" + x.id;
});
stream2.forEach((x) -> {
System.out.println(x);
});
// User [id=9, name=name9]
// User [id=4, name=name4]
}
五、Stream流的常见终止操作
- 匹配、聚合操作
allMatch:接收一个 Predicate 函数,当流中每个元素都符合该断言时才返回true,否则返回false
Stream<Integer> stream = Stream.of(2, 4, 5, 6, 7);
// 判断stream中是否全是偶数
boolean allMatch = stream.allMatch(x -> (x & 1) == 0);
System.out.println(allMatch); // false
noneMatch:接收一个 Predicate 函数,当流中每个元素都不符合该断言时才返回true,否则返回false
Stream<Integer> stream = Stream.of(2, 4, 5, 6, 7);
// 判断stream中是否全是大于0的数
boolean noneMatch = stream.noneMatch(x -> x < 0);
System.out.println(noneMatch);// true
anyMatch:接收一个 Predicate 函数,只要流中有一个元素满足该断言则返回true,否则返回false
Stream<Integer> stream = Stream.of(2, 4, 5, 6, 7);
// 判断stream中是否有大于6的数
boolean anyMatch = stream.anyMatch(x -> x > 6);
System.out.println(anyMatch);// true
findFirst:返回流中第一个元素
Stream<Integer> stream = Stream.of(2, 4, 5, 6, 7);
// 返回流中第一个元素
Optional<Integer> findFirst = stream.findFirst();
System.out.println(findFirst.get());// 2
findAny:返回流中的任意元素
Stream<Integer> stream = Stream.of(2, 4, 5, 6, 7);
// 返回流中的任意元素
Optional<Integer> findAny = stream.findAny();
System.out.println(findAny.get());// 2
count:返回流中元素的总个数
Stream<Integer> stream = Stream.of(2, 4, 5, 6, 7);
// 返回流中元素的总个数
long count = stream.count();
System.out.println(count);// 5
max:返回流中元素最大值
Stream<Integer> stream = Stream.of(2, 4, 5, 6, 7);
// 返回流中元素最大值
Integer max = stream.max(Integer::compare).get();
System.out.println(max);// 7
min:返回流中元素最小值
Stream<Integer> stream = Stream.of(2, 4, 5, 6, 7);
// 返回流中元素最小值
Integer min = stream.min(Integer::compare).get();
System.out.println(min);// 2
- 规约操作
Optional<T> reduce(BinaryOperator accumulator):第一次执行时,accumulator函数的第一个参数为流中的第一个元素,第二个参数为流中元素的第二个元素;第二次执行时,第一个参数为第一次函数执行的结果,第二个参数为流中的第三个元素;依次类推。
Stream<Integer> stream = Stream.of(2, 4, 5, 6, 7);
// 实现数字累加
Optional<Integer> reduce = stream.reduce((a, b) -> {
return a + b;
});
System.out.println(reduce.get()); // 24
T reduce(T identity, BinaryOperator accumulator):流程跟上面一样,只是第一次执行时,accumulator函数的第一个参数为identity,而第二个参数为流中的第一个元素。
Stream<Integer> stream = Stream.of(2, 4, 5, 6, 7);
// 实现数字累加,在原基础上加100
Integer reduce = stream.reduce(100, (a, b) -> {
return a + b;
});
System.out.println(reduce); // 124
<U> U reduce(U identity,BiFunction<U, ? super T, U> accumulator,BinaryOperator<U> combiner):在串行流(stream)中,该方法跟第二个方法一样,即第三个参数combiner不会起作用。在并行流(parallelStream)中,我们知道流被fork join出多个线程进行执行,此时每个线程的执行流程就跟第二个方法reduce(identity,accumulator)一样,而第三个参数combiner函数,则是将每个线程的执行结果当成一个新的流,然后使用第一个方法reduce(accumulator)流程进行规约。
顺序流第三个参数不起作用:
Stream<Integer> stream = Stream.of(2, 4, 5, 6, 7);
// 实现数字累加,在原基础上加100
// 顺序流
Integer reduce = stream.reduce(100, (a, b) -> {
System.out.println("---first---");
return a + b;
}, (a, b) -> {
System.out.println("---second---");
return a + b;
});
System.out.println(reduce); // 124
// ---first---
// ---first---
// ---first---
// ---first---
// ---first---
// 124
并行流,相当于把第一种和第二种方法都用到了:
Stream<Integer> parallelStream = Arrays.asList(new Integer[] {2, 4, 5, 6, 7}).parallelStream();
// 实现数字累加,在原基础上加100
// 顺序流
Integer reduce = parallelStream.reduce(100, (a, b) -> {
System.out.println("---first---" + (a + b));
return a + b;
}, (a, b) -> {
System.out.println("---second---" + (a + b));
return a + b;
});
System.out.println(reduce); // 124
// ---first---105
// ---first---107
// ---first---106
// ---second---213
// ---second---318
// ---first---104
// ---first---102
// ---second---206
// ---second---524
// 524
- 收集操作
将流转换为其他形式,接收一个Collertor接口的实现。
collect:接收一个Collector实例,将流中元素收集成另外一个数据结构,使用Collector 工具库:Collectors。
- List:把流中所有元素收集到List中,使用.collect(Collectors.toList());
Stream<Integer> stream = Stream.of(1, 5, 2, 3, 4);
List<Integer> list = stream.collect(Collectors.toList());
list.forEach(System.out::println);
- Set:把流中所有元素收集到Set中,删除重复项,使用.collect(Collectors.toSet());
Stream<Integer> stream = Stream.of(1, 2, 2, 3, 4);
Set<Integer> set = stream.collect(Collectors.toSet());
set.forEach(System.out::println);
- Map:把流中所有元素收集到Map中,当出现相同的key时会抛异常,使用 .collect(Collectors.toMap());
Stream<Integer> stream = Stream.of(1, 2, 2, 3, 4);
Map<String, Integer> map = stream.collect(Collectors.toMap(
// 定义key
(x) -> {
return "m" + x;
},
// 定义value
(x) -> {
return x;
},
// 当存在key冲突时,解决方法,这里默认使用第一个key对应的value值
(a, b) -> {
return a;
}));
map.forEach((key, value) -> {
System.out.println(key + "=" + value);
});
- 使用collect方法求流中共有几条数据,使用.collect(Collectors.counting())
Stream<Integer> stream = Stream.of(1, 2, 2, 3, 4);
Long count = stream.collect(Collectors.counting());
System.out.println(count); // 5
- 使用collect方法求平均数,使用.collect(Collectors.averagingInt();
Stream<Integer> stream = Stream.of(1, 2, 2, 3, 4);
Double av = stream.collect(Collectors.averagingInt((x) -> {
return x;
}));
System.out.println(av); // 2.4
- 使用collect方法求某个变量的总和,使用.collect(Collectors.summingInt());
Stream<Integer> stream = Stream.of(1, 2, 2, 3, 4);
Integer sum = stream.collect(Collectors.summingInt((x) -> {
return x;
}));
System.out.println(sum); // 12
- 使用collect方法且某个变量中值的最大值,使用.collect(Collectors.maxBy());
Stream<Integer> stream = Stream.of(1, 2, 2, 3, 4);
Optional<Integer> max = stream.collect(Collectors.maxBy(Integer::compare));
System.out.println(max.get()); // 4