【Java8新特性】03 Stream流式数据处理

Java8 由Oracle在2014年发布,是继Java5之后最具革命性的版本了。

Java8吸收其他语言的精髓带来了函数式编程,lambda表达式,Stream流等一系列新特性,学会了这些新特性,可以让你实现高效编码优雅编码。

1. Stream是什么?

Stream是Java8新增的一个接口,允许以声明性方式处理数据集合。Stream不是一个集合类型不保存数据,可以把它看作是遍历数据集合的高级迭代器(Iterator)。

Stream操作可以像Builder一样逐步叠加,形成一条流水线。流水线一般由数据源+零或者多个中间操作+一个终端操作所构成。中间操作可以将流转换成另外一个流,比如使用filter过滤元素,使用map映射提取值。

Stream与lambda表达式密不可分,本文默认你已经掌握了lambda基础知识。

2. Stream的特点

只能遍历(消费)一次

        Stream实例只能遍历一次,终端操作后一次遍历就结束,再次遍历需要重新生成实例,这一点类似于Iterator迭代器。

保护数据源

        对Stream中任何元素的修改都不会导致数据源被修改,比如过滤删除流中的一个元素,再次遍历该数据源依然可以获取该元素。

       filter, map 操作串联起来形成一系列中间运算,如果没有一个终端操作(如collect)这些中间运算永远也不会被执行。

3. 创建Stream实例的方法

(1)使用指定值创建Stream实例

// of为Stream的静态方法

Stream strStream = Stream.of("hello","java8","stream");

// 或者使用基本类型流

IntStream intStream = IntStream.of(1,2,3);

(2)使用集合创建Stream实例(常用方式)

// 使用guava库,初始化一个不可变的list对象

ImmutableList integers = ImmutableList.of(1,2,3);

// List接口继承Collection接口,java8在Collection接口中添加了stream方法

Stream<Integer> stream = integers.stream();

(3)使用数组创建Stream实例

// 初始化一个数组

Integer[] array = {1,2,3};

// 使用Arrays的静态方法stream

Stream<Integer> stream = Arrays.stream(array);

(4)使用生成器创建Stream实例

// 随机生成100个整数

Random random =newRandom();

// 加上limit否则就是无限流了

Stream stream = Stream.generate(random::nextInt).limit(100);

(5)使用迭代器创建Stream实例

// 生成100个奇数,加上limit否则就是无限流了

Stream stream = Stream.iterate(1, n -> n +2).limit(100);

stream.forEach(System.out::println);

(6)使用IO接口创建Stream实例

// 获取指定路径下文件信息,list方法返回Stream类型

Stream pathStream = Files.list(Paths.get("/"));

4. Stream常用操作

Stream接口中定义了很多操作,大致可以分为两大类,一类是中间操作,另一类是终端操作。

(1)中间操作

中间操作会返回另外一个流,多个中间操作可以连接起来形成一个查询。

中间操作有惰性,如果流上没有一个终端操作,那么中间操作是不会做任何处理的。

下面介绍常用的中间操作:

中间操作:map

map是将输入流中每一个元素映射为另一个元素形成输出流。

// 初始化一个不可变字符串

List words = ImmutableList.of("hello","java8","stream");

// 计算列表中每个单词的长度

List<Integer> list = words.stream()

        .map(String::length)

        .collect(Collectors.toList());

// output: 5 5 6

list.forEach(System.out::println);

中间操作:flatMap

List list1 = words.stream()

.map(word -> word.split("-"))

        .collect(Collectors.toList());


// output: [Ljava.lang.String;@59f95c5d,

//            [Ljava.lang.String;@5ccd43c2

list1.forEach(System.out::println);

纳里?你预期是List<String>, 返回却是List<String[]>, 这是因为split方法返回的是String[]

这个时候你可以想到要将数组转成stream, 于是有了第二个版本

Stream<Stream<String>> arrStream = words.stream()

.map(word -> word.split("-"))

        .map(Arrays::stream);

// output: java.util.stream.ReferencePipeline$Head@2c13da15,

// java.util.stream.ReferencePipeline$Head@77556fd

arrStream.forEach(System.out::println);

还是不对,这个问题使用flatMap扁平流可以解决,flatMap将流中每个元素取出来转成另外一个输出流:

Stream<String> strStream = words.stream()

.map(word -> word.split("-"))

        .flatMap(Arrays::stream)

        .distinct();

// output: hello java8 stream

strStream.forEach(System.out::println);

中间操作:filter

filter接收Predicate对象,按条件过滤,符合条件的元素生成另外一个流。

// 过滤出单词长度大于5的单词,并打印出来

List words = ImmutableList.of("hello","java8","hello","stream");

words.stream()

.filter(word -> word.length() >5)

        .collect(Collectors.toList())

        .forEach(System.out::println);

// output: stream

(2)终端操作

终端操作将stream流转成具体的返回值,比如List,Integer等。常见的终端操作有:foreach, min, max, count等。

foreach很常见了,下面举一个max的例子。

// 找出最大的值

List integers = Arrays.asList(6,20,19);

integers.stream()

        .max(Integer::compareTo)

        .ifPresent(System.out::println);

// output: 20

5. 使用Stream重构老代码

假如有一个需求:过滤出年龄大于20岁并且分数大于95的学生。

使用for循环写法:

privateListgetStudents(){

Student s1 =newStudent("xiaoli",18,95);

Student s2 =newStudent("xiaoming",21,100);

Student s3 =newStudent("xiaohua",19,98);

    List<Student> studentList = Lists.newArrayList();

    studentList.add(s1);

    studentList.add(s2);

    studentList.add(s3);

returnstudentList;

}

publicvoidrefactorBefore(){

    List<Student> studentList = getStudents();

// 使用临时list

    List<Student> resultList = Lists.newArrayList();

for(Student s : studentList) {

if(s.getAge() >20&& s.getScore() >95) {

            resultList.add(s);

        }

    }

// output: Student{name=xiaoming, age=21, score=100}

    resultList.forEach(System.out::println);

}

使用for循环会初始化一个临时list用来存放最终的结果,整体看起来不够优雅和简洁。

使用lambda和stream重构后:

publicvoidrefactorAfter() {

List studentLists = getStudents();

// output: Student{name=xiaoming, age=21, score=100}

studentLists.stream().filter(this::filterStudents).forEach(System.out::println);

}

privatebooleanfilterStudents(Student student) {

// 过滤出年龄大于20岁并且分数大于95的学生

returnstudent.getAge() >20&& student.getScore() >95;

}

使用filter和方法引用使代码清晰明了,也不用声明一个临时list,非常方便。

6. 使用Stream常见的误区

(1)误区一:重复消费stream对象

stream对象一旦被消费,不能再次重复消费。

List strings = Arrays.asList("hello","java8","stream");

Stream<String> stream = strings.stream();

stream.forEach(System.out::println);// ok

stream.forEach(System.out::println);// IllegalStateException

上述代码执行后报错:

java.lang.IllegalStateException: stream has already been operated upon or closed

(2)误区二:修改数据源

在流操作的过程中尝试添加新的string对象,结果报错:

List strings = Arrays.asList("hello","java8","stream");

// expect: HELLO JAVA8 STREAM WORLD, but throw UnsupportedOperationException

strings.stream()

        .map(s -> {

strings.add("world");

returns.toUpperCase();

        }).forEach(System.out::println);

注意:一定不要在操作流的过程中修改数据源。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351