Java Stream

1 篇文章 0 订阅
1 篇文章 0 订阅

Java Stream

1. 概念说明

Stream这个词很容易造成误解,这里先做一个澄清,Java中的流:

  • 非IO流,非数据流,而是集合的功能增强
  • 非集合元素,非数据结构,也不保存数据,但却为数据操作而生

其实流式操作在动态语言中并不少见,map/filter/reduce...在动态语言中都是耳熟能详的用法,又叫迭代器。

所以,在Java中也类似。但Java中已经有Iterator的概念,所以Java Stream又像一个高级的Iterator,一个定义了map/filter/reduce的接口。查看源码可以看到相关定义:

public interface Stream<T> extends BaseStream<T, Stream<T>> {

	Stream<T> filter(Predicate<? super T> predicate);
	<R> Stream<R> map(Function<? super T, ? extends R> mapper);
	IntStream mapToInt(ToIntFunction<? super T> mapper);
	...
	T reduce(T identity, BinaryOperator<T> accumulator);
	...
}

所以我对Java Stream的理解是,一个聚合操作(map/filter/reduce...)的迭代器载体。也是函数式编程的一个时代产物。

2. 几个优点

一个新产物,必定是有很多好处的:

  • 张口就来的代码简洁(一行代码高低多个for循环),操作简单,逻辑清晰,可读性高等blabla
  • 快速实现并行操作,只需用parallelStream()即可
  • 其他好处在下面的使用讲解中感受

3. 使用介绍

流的使用分为三步:构造流->操作流->返回操作结果(集合/数组)

3.1 构造流

在实际操作中,构造流的过程往往只是一个过程态,这里为了方便讲解,会进行声明和实例化。

构造流有以下几种方法:

  • 直接实例化(使用少)
  • 数组/集合转化(少用最多)
  • 基本数值型转化

// 直接实例化
Stream stream_1 = Stream.of("aaa", "bbb", "ccc");

// Aarray转
String[] strs = new String[]{"aaa", "bbb", "ccc"};
Stream stream_2 = Stream.of(strs);
Stream stream_3 = Arrays.stream(strs);

// List转
List<String> list = Arrays.asList(strs);
Stream stream_4 = list.stream();

// 基本数值型,三种包装类型IntStream、LongStream、DoubleStream
Stream stream_5 = IntStream.of(1, 2, 3); //跟直接实例化类似
Stream stream_6 = (Stream) IntStream.of(intArr);
3.2 操作流

将数据转化为流之后,就可以进入主题,进行各种流式操作了。

直接来一个小例子:将字符串数组转换为大写并打印

String[] strs = {"aaa", "bbb", "ccc"};
Stream.of(strs).map(String::toUpperCase).forEach(System.out::println);
/*
AAA
BBB
CCC
*/

一行代码轻松搞定!完事!

当然啦,没这么简单。虽然一行代码,但里面还是藏着很多知识点的。

第一个问题:为什么要用forEach不用map

Stream.of(strs).map(String::toUpperCase).map(System.out::println);

javascript中,a.map(e=>e.toUpperCase()).map(console.log)这是允许的。

而到了Java,上面的代码将会编译报错

Error:(43, 54) java: 不兼容的类型: 无法推断类型变量 R
    (参数不匹配; 方法引用中的返回类型错误
      void无法转换为R)

其实,在流式操作里面又分为下面两种:

  • Intermediate: 中间。可以理解为对数据的操作,这种传的lambda表达式必须有返回值。就是我们常用的map/filter/reduce
  • Terminal: 终止。可以理解为结束,操作完就结束了,无法继续操作了,这种传的lambda表达式必须没有返回值。比如我们用得最多的forEach

回到上面的例子:

  • 编译报错就是因为最后一个map(中间操作)的参数是没有返回值的,因此要使用forEach
  • 而javascript中为什么又可以呢? 这是因为javascript中每一条语句都有一个返回值,console.log返回的是undefined

3.3 转换流

关键的操作都结束了,最后就是要使用流式操作结束的数据了。

上一节提到的Terminal操作类型就是一个最常用的使用。可能不太好理解。

翻译一下

  • 比如.forEach(System.out::println)就是对操作完的数据(转大写)进行使用(打印)
  • 还有最常用的将操作完的数据转成列表.collect(Collectors.toList())

3.4 补充

在测试过程中,多写了一行代码:

String[] strs = new String[]{"aaa", "bbb", "ccc"};
Stream<String> stream = Stream.of(strs);
stream.map(String::toUpperCase).forEach(System.out::println);
stream.filter(e-> e.contains("a")).forEach(System.out::println);

结果:运行时报错:IllegalStateException: stream has already been operated upon or closed

知识点+1:
流在使用结束(即Terminal)之后,这个流就已经关闭了。所以无法对一个流进行两次Terminal运算。
而我们日常合并使用的filter().map()的操作又是允许的,因为Intermediate的操作结束其实还是返回一个流的。

总结

最后再说两句:

为什么同样是聚合操作,在js/python等动态语言中可以直接使用,而在Java中要再封装一个Stream呢。
个人猜想,在Java 7里面已经有个各种各样的集合类,现在要对集合进行功能扩展,增加聚合才做的静态函数。

有两种选择:

  1. Iterator新增map/filter等多个接口,每个集合类中去实现接口
  2. 新增一个Stream类,Iterator新增转化为Stream的接口
    Java 8自然选择了后者。

因此,可以把Java中的Stram理解成一个包装。你要使用,就得按照它的包装规则来。

比如你要参加万圣节派对,就得先化一个可爱的妆。派对结束,你就可以卸妆了(当然也可以不卸)。
你要游泳,就得带泳装;
你要上班,就得带工卡;
你要编程,就得秃头

Java Stram还有很多更高级的用法,比如parallelStream,比如Short-circuiting,比如flatMap等等,大家有兴趣的可以自行学习。

如有错误,欢迎指正~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值