Stream
简介
Stream 作为 Java8 的一大亮点,它与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念,Stream 是对集合的包装,通常和lambda一起使用。使用stream可以支持许多操作,如 map, filter, limit, sorted, count, min, max, sum, collect 等等。
Stream的特性
Steam主要具有一下三点特性
- stream不存储数据
- stream不改变数据
- stream的延迟执行特性
通常我们在数组或集合的基础上创建Stream,Stream不会专门的存储数据,对Stream的操作也不会影响到创建它的数组或集合
对于同一个Stream的聚合、消费或收集操作只能进行一次,再次操作会报错,一个流只能发生一次操作
public static void main(String[] args) {
int[] arr = new int[]{1, 2, 3};
// 对同一个Stream做两次消费
IntStream intStream = Arrays.stream(arr);
// forEach
intStream.forEach(System.out::println);
// 第二次forEach发生异常
intStream.forEach(System.out::println);
}
而对于新的流则不会发生异常
public static void main(String[] args) {
int[] arr2 = new int[]{1, 2, 3};
Arrays.stream(arr2).forEach(System.out::println);
// new 正常输出
Arrays.stream(arr2).forEach(System.out::println);
}
stream的操作是延迟执行的,在列出字符串长度大于3的例子中,在collect方法执行之前,filter、sorted、map方法还未执行,只有当collect方法执行时才会触发之前转换操作
public boolean filter(String s) {
System.out.println("begin compare");
return s.length() > 3;
}
@Test
public void test2() {
List<String> strs = new ArrayList<String>() {
{
add("abc");
add("abcd");
}
};
Stream<String> stream = strs.stream().filter(this::filter);
System.out.println("split-------------------------------------");
List<String> list = stream.collect(Collectors.toList());
System.out.println(list);
}
结果如下:
split-------------------------------------
begin compare
begin compare
[abcd]
由此可以看出,在执行完 filter时,没有实际执行filter中的方法,而是等到执行collect时才会执行,即是延迟执行。
注意:
- 由于stream的延迟执行特性,在聚合操作执行之前修改数据源是允许的;
- 当我们操作一个流的时候,并不会修改底层的集合(即使集合是线程安全的),若修改了原来的集合,就无法定义流操作的输出。
/**
* 延迟执行特性,在聚合操作之前都可以添加相应元素
*/
@Test
public void test3() {
List<String> wordList = new ArrayList<String>() {
{
add("a");
add("b");
}
};
Stream<String> words = wordList.stream();
wordList.add("END");
long n = words.distinct().count();
System.out.println(n);
}
输出结果为 3
延迟执行特性会产生干扰
@Test
public void test4(){
List<String> wordList = new ArrayList<String>() {
{
add("a");
add("b");
}
};
Stream<String> words1 = wordList.stream();
words1.forEach(s -> {
System.out.println("s->"+s);
if (s.length() < 4) {
System.out.println("select->"+s);
wordList.remove(s);
System.out.println(wordList);
}
});
}
输出结果
s->a
select->a
[b]
s->null
java.lang.NullPointerException
创建Stream
public static void main(String[] args) {
// 1.通过集合创建流
//Collections.singletonList("a");
List<String> list = Arrays.asList("a", "b", "c");
// 普通流
Stream<String> stream1 = list.stream();
// 并行流(多个线程处理)
Stream<String> stream2 = list.parallelStream();
// 2.通过数组创建流
int[] arr = new int[]{1, 2, 3, 4};
IntStream stream3 = Arrays.stream(arr);
// 3.Stream.of()创建流
Stream<Integer> integerStream = Stream.of(1, 2, 3, 4);
integerStream.forEach(System.out::println);
// 4.创建规律的无限流
Stream<Integer> stream = Stream.iterate(0, x -> x + 2).limit(4);
// 5.创建无限流
Stream.generate(() -> "number" + new Random().nextInt()).limit(3);
// 6.创建空流
Stream<Object> empty = Stream.empty();
}
Stream操作分类
操作分为 中间操作 和 终结操作
- 中间操作分为 无状态 和 有状态 操作
- 无状态是指元素的处理不受之前元素的影响;
- 有状态是指该操作只有拿到所有元素之后才能继续下去;
- 终结操作分为 短路 和 非短路 操作
- 短路是指遇到某些符合条件的元素就可以得到最终结果;
- 非短路是指必须处理完所有元素才能得到最终结果;
我们通常还会将中间操作称为 懒操作,也正是由这种 懒操作结合终结操作、数据源构成的处理管道(Pipeline),实现了 Stream 的高效。
中间操作
无状态
/**
* 中间操作:无状态
* filter:过滤流,过滤流中的元素
* map:转换流,将一种类型的流转换为另外一种类型的流
* flapMap:拆解流,将流中的每一个元素拆解组成一个新流
*/
public class OperateStream {
public static void main(String[] args) {
Integer[] arr = new Integer[]{1, 2, 3, 4, 5};
// 1.filter过滤流中的元素
Arrays.stream(arr).filter(x -> x > 3 && x < 6).forEach(System.out::println);
List<String> list = Arrays.asList("ADadc", "dweqdAAS");
// 2.map转换流,将一种类型的流转换为另一种类型
list.stream().map(x -> x.toUpperCase()).forEach(System.out::println);
// 3.flapMap 拆解流,将流中的每一个元素拆解,组成一个新的流
String[] str1 = {"a", "b"};
String[] str2 = {"c", "d"};
String[] str3 = {"e", "f"};
Stream.of(str1, str2, str3).flatMap(Arrays::stream).forEach(System.out::println);
}
}
有状态
/**
* 中间操作:有状态
* distinct 去重
* sorted 排序
* limit 获取前面的指定数量元素
* skip 跳过前面指定数量的元素,获取后面的元素
* concat 把两个stream合并成一个stream
*/
public class OperateStream2 {
public static void main(String[] args) {
// 1.sorted 按照字符串长度排序
String[] str = {"abc", "a", "b", "cd", "defg"};
System.out.println("长度排序");
Arrays.stream(str).sorted(Comparator.comparing(String::length)).forEach(System.out::println);
// 2.reversed() 倒序排列
System.out.println("长度倒序");
Arrays.stream(str).sorted(Comparator.comparing(String::length).reversed()).forEach(System.out::println);
// Comparator.reverseOrder():也是用于翻转顺序,用于比较对象(Stream里面的类型必须是可比较的)
// 3.按首字母倒序排列
System.out.println("首字母倒序排序");
Arrays.stream(str).sorted(Comparator.reverseOrder()).forEach(System.out::println);
// 4.自然排序 按首字母的顺序
Arrays.stream(str).sorted(Comparator.naturalOrder()).forEach(System.out::println);
// 5.thenComparing
// 先按首字母排序,之后按照长度排序
System.out.println("先按首字母排序,再按照长度排序");
Arrays.stream(str).sorted(Comparator.comparing(OperateStream2::firstChar).thenComparing(String::length)).forEach(System.out::println);
// 6.limit 从流中获取前n个元素
System.out.println("获取前n个元素");
Stream.generate(() -> new Random().nextInt()).limit(3).forEach(System.out::println);
// 7.skip跳过前n个数据
System.out.println("skip跳过第一个元素");
Stream.iterate(1, x -> x + 2).skip(1).limit(3).forEach(System.out::println);
// 8.concat 把两个Stream合成一个Stream, 合并的Stream类型必须相同
Stream<Integer> stream1 = Stream.iterate(1, x -> x + 2).limit(3);
Stream<Integer> stream2 = Stream.iterate(1, x -> x + 2).skip(1).limit(3);
// 9.合并
System.out.println("去重合并");
Stream.concat(stream1, stream2).distinct().forEach(System.out::println);
}
public static char firstChar(String x) {
// 返回首字母
return x.charAt(0);
}
}
终结操作
非短路操作
/**
* 终结操作:非短路操作
* forEach:遍历
* toArray:将流转换为Object数组
* reduce:归约,可以将流中的元素反复结合起来,得到一个值
* collect:收集,将流转换为其他形式,比如List、Set、Map
* groupBy:分组
* max:返回流的最大值,无方法参数
* min:最小值
* count:返回流中的元素总个数,无方法参数
* summaryStatistics:获取汇总统计数据,比如最大值,最小值,平均值等
*/
public class OperateStream3 {
public static void main(String[] args) {
// 1.forEach
List<String> list = new ArrayList<String>() {
{
add("a");
add("b");
}
};
list.stream().forEach(System.out::println);
//list.forEach(System.out::println);
// 2.reduce 归约
Optional<Integer> optional = Stream.of(1, 2, 3).filter(x -> x > 1).reduce((x, y) -> x + y);
System.out.println("reduce 归约");
System.out.println(optional.get());
List<Book> books = Arrays.asList(
new Book(1, 22, "扬尼斯阿德托昆博", "北京"),
new Book(1, 20, "扬尼斯阿德托昆博", "北京"),
new Book(1, 24, "扬尼斯阿", "北京"),
new Book(1, 25, "勒布朗詹姆斯", "天津")
);
// 3.collect收集,将流转换为其他形式 略。。。
// Collectors.toList() Collectors.toSet() Collectors.toMap(k, v)
Map<String, Book> map = books.stream().collect(Collectors.toMap(b -> b.getName(), Function.identity(), (k1, k2) -> k2));
// 4.groupingBy 分组
Map<String, List<Book>> map2 = books.stream().collect(Collectors.groupingBy(b -> b.getName()));
System.out.println("分组");
System.out.println(map2);
// 5.partitioningBy 分组
// 如果只有两类,使用partitioningBy 比 groupBy 效率更高
Map<Boolean, List<Book>> map1 = books.stream().collect(Collectors.partitioningBy(x -> x.getAge() > 22));
// 6.max、min
String[] str = new String[]{"a", "ab", "abc", "送你一朵小红花"};
System.out.println("max、min");
Stream.of(str).max(Comparator.comparing(String::length)).ifPresent(System.out::println);
Stream.of(str).min(Comparator.comparing(String::length)).ifPresent(System.out::println);
// 7.count
long count = Stream.of(str).count();
System.out.println("count");
System.out.println(count);
// 8.summaryStatistics 获得Stream中元素的各种汇总数据
List<Integer> number = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
IntSummaryStatistics summaryStatistics = number.stream().mapToInt(x -> x).summaryStatistics();
System.out.println("list中最大的数字:" + summaryStatistics.getMax());
System.out.println("list中最小的数字:" + summaryStatistics.getMin());
System.out.println("list中所有数字总和:" + summaryStatistics.getSum());
System.out.println("list中所有数字平均值:" + summaryStatistics.getAverage());
}
}
短路操作
/**
* 终结操作:短路操作
* anyMatch:检查是否有一个元素匹配,方法参数为断言型接口
* allMatch:检查是否匹配所有元素,方法参数为断言型接口
* findFirst:返回第一个元素,无方法参数
* findAny:返回当前流的任意元素,无方法参数
* noneMatch:检查是否没有匹配所有元素,方法参数为断言型接口
*/
public class OperateStream4 {
public static void main(String[] args) {
String[] str = new String[]{"b", "ab", "abc", "abcd", "abcde"};
// 1. anyMatch 检查是否有一个元素匹配
boolean b = Stream.of(str).anyMatch(x -> x.startsWith("a"));
System.out.println("anyMatch------");
System.out.println(b);
// 2.allMatch
boolean a = Stream.of(str).allMatch(x -> x.startsWith("a"));
System.out.println("allMatch------");
System.out.println(a);
// 3. noneMatch 检查是否没有匹配所有元素,只要有匹配的就返回true
boolean a1 = Stream.of(str).noneMatch(x -> x.startsWith("a"));
System.out.println("noneMatch------");
System.out.println(a1);
// 4. findFirst 返回大于3的第一个元素
String s = Stream.of(str).parallel().filter(x -> x.length() > 3).findFirst().orElse("nothing");
System.out.println("findFirst 返回大于3的第一个元素" + s);
// 5. findAny 找到任意匹配的元素
// 对并行流十分有效,只要在任何片段发现了一个匹配的元素,就会结束整个运算
Optional<String> optional = Stream.of(str).parallel().filter(x -> x.length() > 2).findAny();
System.out.println("findAny 找到任意匹配的元素");
optional.ifPresent(System.out::println);
}
}
Optional类型
/**
* Optional类型
* 通常聚合操作会返回一个 Optional类型,Optional表示一个安全的指定结果类型,所谓的安全是指避免直接调用返回类型的null值
* 而造成空指针异常,optional.ifPresent()可以判断返回值是否为空
* optional.get()获取返回值
*/
public class OptionalTest {
public static void main(String[] args) {
List<String> list = new ArrayList<String>() {{
add("user1");
add("user2");
}};
Optional<String> optional = Optional.of("user3");
// 1.若optional不为空,list.add();
optional.ifPresent(list::add);
list.forEach(System.out::println);
// 2.Optional可以在没有值时指定一个返回值
Integer[] arr = new Integer[]{1, 3, 5, 6};
Integer integer = Stream.of(arr).filter(x -> x > 7).max(Comparator.naturalOrder()).orElse(-1);
System.out.println(integer);
Integer integer1 = Stream.of(arr).filter(x -> x > 7).max(Comparator.naturalOrder()).orElseGet(() -> -1);
System.out.println(integer1);
// 抛出异常
Integer integer2 = Stream.of(arr).filter(x -> x > 7).max(Comparator.naturalOrder()).orElseThrow(RuntimeException::new);
System.out.println(integer2);
}
}
原始类型流
在数据量比较大的情况下,将基本数据类型(int,double…)包装成相应对象流的做法是低效的,因此,我们也可以直接将数据初始化为原始类型流,在原始类型流上的操作与对象流类似,我们只需要记住两点:
- 原始类型流的初始化
- 原始类型流与流对象的转换
/**
* 原始类型流
* 原始类型流的初始化
* 原始类型流与对象的转换
*/
public class OriginalStream {
public static void main(String[] args) {
// 1.原始类型流的初始化
IntStream intStream = IntStream.of(1, 3);
intStream.forEach(System.out::println);
// 包含右边界
IntStream intStream1 = IntStream.rangeClosed(0, 1);
intStream1.forEach(System.out::println);
// 不包含有边界
IntStream intStream2 = IntStream.range(0, 1);
intStream2.forEach(System.out::println);
// 2.流与原始类型流的转换
IntStream intStream4 = IntStream.of(1, 2, 3);
// int -> Integer 装箱 boxed
// List<Integer> collect = intStream4.boxed().collect(Collectors.toList());
Stream<Integer> stream = intStream4.boxed();
// Integer -> int 拆箱
IntStream intStream3 = stream.mapToInt(Integer::new);
intStream3.forEach(System.out::println);
}
}
并行流
/**
* 并行流
* 将普通流转换为并行流,只需要调用顺序流的 parallel()方法即可
* 调用peek方法可以看 串行流和并行流的执行顺序
* peek:追踪流内的数据
*/
public class ParallelStream {
public static void main(String[] args) {
// 串行流
Stream<Integer> peekStream = Stream.iterate(1, x -> x + 1).limit(10);
peekStream.peek(ParallelStream::peek1).filter(x -> x > 5)
.peek(ParallelStream::peek2).filter(x -> x < 8)
.peek(ParallelStream::peek3)
.forEach(System.out::println);
// 并行流
Stream<Integer> parallelStream = Stream.iterate(1, x -> x + 1).limit(10).parallel();
parallelStream.peek(ParallelStream::peek1).filter(x -> x > 5)
.peek(ParallelStream::peek2).filter(x -> x < 8)
.peek(ParallelStream::peek3)
.forEach(System.out::println);
}
// 定义方法
public static void peek1(int x) {
System.out.println(Thread.currentThread().getName() + ":->peek1->" + x);
}
public static void peek2(int x) {
System.out.println(Thread.currentThread().getName() + ":->peek2->" + x);
}
public static void peek3(int x) {
System.out.println(Thread.currentThread().getName() + ":->final result->" + x);
}
}
串行流 peekStream 执行结果
main:->peek1->1
main:->peek1->2
main:->peek1->3
main:->peek1->4
main:->peek1->5
main:->peek1->6
main:->peek2->6
main:->final result->6
6
main:->peek1->7
main:->peek2->7
main:->final result->7
7
main:->peek1->8
main:->peek2->8
main:->peek1->9
main:->peek2->9
main:->peek1->10
main:->peek2->10
并行流 parallelStream执行结果
ForkJoinPool.commonPool-worker-2:->peek1->3
ForkJoinPool.commonPool-worker-1:->peek1->2
ForkJoinPool.commonPool-worker-2:->peek1->5
ForkJoinPool.commonPool-worker-3:->peek1->9
ForkJoinPool.commonPool-worker-2:->peek1->4
main:->peek1->7
ForkJoinPool.commonPool-worker-1:->peek1->1
ForkJoinPool.commonPool-worker-2:->peek1->8
ForkJoinPool.commonPool-worker-3:->peek2->9
ForkJoinPool.commonPool-worker-2:->peek2->8
ForkJoinPool.commonPool-worker-1:->peek1->10
ForkJoinPool.commonPool-worker-3:->peek1->6
ForkJoinPool.commonPool-worker-1:->peek2->10
ForkJoinPool.commonPool-worker-3:->peek2->6
ForkJoinPool.commonPool-worker-3:->final result->6
6
main:->peek2->7
main:->final result->7
7
总结:
我们将stream.filter(x -> x > 5).filter(x -> x < 8).forEach(System.out::println)的过程想象成管道,我们在管道上加入的peek相当于一个阀门,透过这个阀门查看流经的数据
(1)当我们使用顺序流时,数据按照源数据的顺序依次通过管道,当一个数据被filter过滤,或者经过整个管道而输出后,第二个数据才会开始重复这一过程
(2)当我们使用并行流时,系统除了主线程外启动了3个线程(和电脑配置有关)来执行处理任务,因此执行是无序的,但同一个线程内处理的数据是按顺序进行的。
sorted( )、distinct( )等对并行流的影响
对于并行流执行sorted()、distinct(),会使得运行时间大大增加,这个说法是错误的,测试表明不管是filter()还是distinct().sorted(),并行流都比串行流高效。
/**
* 测试distinct() 对 串行流 和 并行流 执行效率的影响
*/
public class TestParallelDistinct {
public static void main(String[] args) {
// 生成一亿条 0~100之间的数据
Random random = new Random();
List<Integer> list = Stream.generate(() -> random.nextInt(100)).limit(100000000).collect(Collectors.toList());
long begin1 = System.currentTimeMillis();
list.stream().filter(x -> x > 10).filter(x -> x < 80).count();
long end1 = System.currentTimeMillis();
System.out.println("串行流执行时间:" + (end1 - begin1));
long begin2 = System.currentTimeMillis();
list.stream().parallel().filter(x -> x > 10).filter(x -> x < 80).count();
long end2 = System.currentTimeMillis();
System.out.println("并行流执行时间:" + (end2 - begin2));
long beginDis = System.currentTimeMillis();
list.stream().filter(x -> (x > 10)).filter(x -> x < 80).distinct().sorted().count();
long end1Dis = System.currentTimeMillis();
System.out.println("串行流执行排序时间:" + (end1Dis - beginDis));
list.stream().parallel().filter(x -> (x > 10)).filter(x -> x < 80).distinct().sorted().count();
long end2Dis = System.currentTimeMillis();
System.out.println("并行流执行排序时间:" + (end2Dis - end1Dis));
}
}
执行结果
串行流执行时间:943
并行流执行时间:656
串行流执行排序时间:1957
并行流执行排序时间:1125
并行流比串行流高效
总结
lambda表达式结合stream API对集合的处理非常方便,在平常项目中可以非常的省时间,提高写代码的效率。