Java8-引入流(1)

集合是Java中使用最多的API。要是没有集合,还能做什么呢?几乎每个java应用程序都会制造处理集合。集合对于很多的编程任务来说都是非常基本的:它们可以让你把数据分组并且加以处理。为了解释集合是怎么工作的,想象一下你准备一系列菜,组成一张菜单,然后再遍历一次,把每盘菜的热量加起来。你可能想选出那些热量比较低的菜,组成一张健康的特殊菜单。尽管集合对于几乎任何一个Java应用程序都是不可或缺的,但是集合操作缺远远算不上完美。

A.很多任务逻辑都涉及类似于数据库的操作,比如对几道菜按照类别进行分类(比如全素菜肴),或者查找出最贵的菜。你自己用迭代器重新实现过这些操作多少遍?大部分数据库都允许你声明式地指定这些操作。比如,以下sql查询语句就可以选出热量较低的菜肴名称:select name from dishes where calorie < 400。你看,你不需要实现如何根据菜肴的属性进行筛选(比如利用得迭代器和累加器),你只需要表达你想要什么。这个基本的思路意味着,你用不着担心怎么去显示地实现这些查询语句。

B.要是要处理大量元素有该怎么办呢?为了提供性能,你需要并行处理,并且利用多核架构。但是写并行代码比用迭代器还要复杂,而且调试起来也够难受的!

那Java语言的设计者能做些什么,来帮助节约宝贵的时间,让你这个程序员活的轻松一点儿呢?你可能已经猜到了,答案就是

1.流是什么

流是Java API的新成员,它允许你以声明性方式处理数据集合(通过查询语句来表达,而不是临时编写一个实现)。就现在来说,你可以把它们看成遍历数据集的高级迭代器。此外,流还可以透明地并行处理,你无需写任何多线程代码了!我们简单看看使用流的好处吧。下面两端代码都是返回低热量的菜肴名称的,并且按照卡路里排序,一个使用Java7,写的,另一个是用Java8的流写的。比较一下。不用担心Java8代码怎么写的:

之前(Java7):

 1         List<Dish> lowCaloricDishes = new ArrayList<>();
 2         List<Dish> menu = Arrays.asList(new Dish("1", 100), new Dish("1", 300), new Dish("1", 500),
 3                 new Dish("1", 1000));
 4         for (Dish d : menu) {  //使用迭代器筛选元素
 5             if(d.getCalories() < 400){
 6                 lowCaloricDishes.add(d);
 7             }
 8         }
 9         Collections.sort(lowCaloricDishes, new Comparator<Dish>() {  // 使用匿名类菜肴排序
10
11             @Override
12             public int compare(Dish o1, Dish o2) {
13                 return Integer.compare(o1.getCalories(), o2.getCalories());
14             }
15         });
16         List<String> lowCaloricDishesName = new ArrayList<>();
17         for(Dish d: lowCaloricDishes){
18             lowCaloricDishesName.add(d.getName()); //处理排序后的菜名列表
19         }

在这段代码中,你用了一个“垃圾变量”lowCaloricDishes。它唯一的作用就是作为一次性中间容器。在Java8中,实现的细节被放在它本该归属的库里了。

1         List<String> lowCaloricDishesName = new ArrayList<>();
2         lowCaloricDishesName =
3                 menu.stream()
4                 .filter(d -> d.getCalories() < 400)  //选出400卡路里以下的菜肴
5                 .sorted(Comparator.comparing(Dish::getCalories)) //按照卡路里排序
6                 .map(Dish::getName)  //提取菜肴的名称
7                 .collect(Collectors.toList()); //将所有的名称保存在List中

为了利用多核架构并行执行这段代码,你只需要把stream()换成parallelStream();

1         lowCaloricDishesName =
2                 menu.parallelStream()
3                 .filter(d -> d.getCalories() < 400)  //选出400卡路里以下的菜肴
4                 .sorted(Comparator.comparing(Dish::getCalories)) //按照卡路里排序
5                 .map(Dish::getName)  //提取菜肴的名称
6                 .collect(Collectors.toList()); //将所有的名称保存在List中

你可能会想,在调用parallelStream方法的时候到底发生了什么。用了多少个线程?对性能有多大的提升?这些问题在以后会详细的讨论。现在,你看出来了,从软件工程师的角度来看,新的方法有几个显而易见的好处。

A.代码是以声明性方法来写的:说明了想要完成什么(筛选热量低的菜肴)而不是说明了如何实现一个操作(利用循环和if条件等控制流语句)。你在之前也看到了,这种方法加上行为参数化让你可以轻松应对变化的需求:你很容易再创建一个代码版本,利用Lamba表达式来筛选高卡路里的菜肴,而不是去复制粘贴代码。

B .你可以把几个基础操作筛选链接起来,来表达复杂的数据处理流水线(在filter后面接上sorted、map和collect操作, 如下图所示),同时保持代码清晰可读。filter的结果被传给了sorted方法,再传给map方法,最后传给collect方法。

因为filter、sorted、map和collect等操作是与具体线程模型无关的高层次构件,所以它们的内部实现可以是单线程的,也可能透明地充分利用你的多级架构!在实践中,这意味着你用不着为了让某些数据处理任务并行而去操心线程和锁了,Stream API都替你想好了!

2.流简介

要讨论流,我们先来谈谈集合,这是最容易上手的方法。Java8中的集合支持一个新的stream方法,它会返回一个流(接口定义在java.util.stream.Stream里)。你在后面会看到,还有很多的方式来可以得到流,比如利用数值范围或从I/O资源生成的流元素。

那么,流到底是什么呢?简短的定义就是"从支持数据处理操作的源生成的元素序列"。让我们一步步剖析这个定义。

A.元素序列--就像集合一样,流也提供了一个接口,可以访问特定元素类型的一组有序值。因为集合是数据结构,所以它的主要目的是以特定的时间/空间复杂度存储和访

问元素(ArrayList和LinkedList)。但是流的目的在于表达计算,比如你前面简单的filter、sorted和map。集合讲的是数据,流讲得是计算。我们在后面几节详细解释这个思想。

B.源--流会使用一个提供数据的源,如集合、数组或者输入/输出资源。请注意,从有序集合生成流时会保留原有的顺序。由列表生成的流,其元素顺序与列表一致。

 C.数据处理操作--流的数据处理功能支持类似于数据库的操作,以及函数式编程语言中的常用操作,如filter、map、reduce、find、match、sort等等。流操作可以顺序执行,也可以并行执行。

此外,流操作有两个重要的特点。

A.流水线--很多流操作本身会返回一个流,这样多个操作就可以链接起来,形成一个大的很大的流水线。这让我们以后的一些优化成为了可能,如延迟和短路。流水线的操作可以可以看做对数据源进行数据库式查询。

B.内部迭代--与使用迭代器显示迭代的集合不同,流的迭代操作是在背后进行的。

让我们来看一段能够体现所有这些概念的代码:

Dish类的定义:

 1 package com.example.demo13;
 2
 3 public class Dish {
 4     private final String name;
 5     private final boolean vegetarion;
 6     private final int calories;
 7     private final Type type;
 8     public enum Type{
 9         MEAT, FISH, OTHER
10     }
11     public Dish(String name, boolean vegetarion, int calories, Type type) {
12         this.name = name;
13         this.vegetarion = vegetarion;
14         this.calories = calories;
15         this.type = type;
16     }
17     public String getName() {
18         return name;
19     }
20     public boolean isVegetarion() {
21         return vegetarion;
22     }
23     public int getCalories() {
24         return calories;
25     }
26     public Type getType() {
27         return type;
28     }
29
30 }

main方法所在类的定义:

 1 package com.example.demo13;
 2
 3 import java.util.Arrays;
 4 import java.util.List;
 5 import java.util.stream.Collectors;
 6
 7 public class Demo13 {
 8     public static void main(String[] args) {
 9         List<Dish> menu = Arrays.asList(
10                 new Dish("pork", false, 800, Dish.Type.MEAT),
11                 new Dish("beef", false, 700, Dish.Type.MEAT),
12                 new Dish("chicken", false, 400, Dish.Type.MEAT),
13                 new Dish("french fries", true, 530, Dish.Type.OTHER),
14                 new Dish("rice", true, 350, Dish.Type.OTHER),
15                 new Dish("season fruit", true, 120, Dish.Type.OTHER),
16                 new Dish("pizza", true, 550, Dish.Type.OTHER),
17                 new Dish("prawns", false, 300, Dish.Type.FISH),
18                 new Dish("salmon", false, 450, Dish.Type.FISH));
19         List<String> treenHighCaloricDishNames =
20                 menu.stream().  //从menu获得流(菜肴列表)
21                 filter(d -> d.getCalories() > 300). //建立流水线的操作:首先选出高热量的菜肴
22                 map(Dish::getName). //获取菜名
23                 limit(3).  //只选择前面三个
24                 collect(Collectors.toList());  //结果保存在另一个List集合中
25         System.out.println(treenHighCaloricDishNames);
26     }
27 }
时间: 2024-10-07 18:49:28

Java8-引入流(1)的相关文章

【转】Java8 Stream 流详解

  当我第一次阅读 Java8 中的 Stream API 时,说实话,我非常困惑,因为它的名字听起来与 Java I0 框架中的 InputStream 和 OutputStream 非常类似.但是实际上,它们完全是不同的东西. Java8 Stream 使用的是函数式编程模式,如同它的名字一样,它可以被用来对集合进行链状流式的操作. 本文就将带着你如何使用 Java 8 不同类型的 Stream 操作.同时您还将了解流的处理顺序,以及不同顺序的流操作是如何影响运行时性能的. 我们还将学习终端

简说JAVA8引入函数式的问题

JAVA8中加入lambda演算是一个令人兴奋的新特性——虽然这个新特性来得太迟了,目前的主流开发语言中,JAVA似乎是最后一个支持函数式思维的语言. 虽然晚了点,但总比没有好——况且我认为它的实现还是可以的,至少比C++的实现好一点(C++编译器不能自动很好的处理闭包环境,却要求程序员在代码中指定要引入到lambda表达式中的变量(捕获列表)——C++的类型系统过于丰富,如果没有捕获列表,则编译器无法得知应该通过“值”捕获还是通过“引用”捕获,而JAVA的类型系统简单很多,没有这个问题). 以

【java多线程】java8的流操作api和fork/join框架

一.测试一个案例,说明java8的流操作是并行操作 1.代码 package com.spring.test.service.forkjoin; import java.util.ArrayList; import java.util.List; /** * */ public class Java8Test { public static void main(String[] args) { testList(); } public static void testList(){ //源数据

Java8 Stream流

Java8 Stream流 Java8关于map和flatMap的代码片段思考 Java8初体验(二)Stream语法详解 distinct() /* 返回一个流包含不同的元素(根据equals方法判断,null值并不会报空指针异常,会保留一个null). 对于有序的流保证稳定性,保留最先出现的元素,对于无序的流不保证稳定性. */ /** * Returns a stream consisting of the distinct elements (according to * {@link

在使用Java8并行流时的问题分析

最近在使用Java8的并行流时遇到了坑,线上排查问题时花了较多时间,分享出来与大家一起学习与自查 // 此处为坑 List<Java8Demo> copy = Lists.newArrayList(); numbers.parallelStream().forEach(item -> { copy.add(new Java8Demo(item)); }); 上图用到了parallelStrem并行流,在循环内部往共享变量copy内写值,由于ArrayList本身不具备线程安全性,导致得到

java8之流的基本使用(二)

概述 流(stream())是java8的一个新特性,主要的作用就是将各种类型的集合转换为流,然后的方便迭代数据用的.例如: //将List类型的集合转换为流 list.stream() 转换为流之后可以进行一系列的迭代操作,比自己去拿出list的值一个个操作要方便的多. 使用流的好处 声明性 -- 更简洁.更易读 可复合 -- 更灵活 可并行 -- 性能更好 流的使用方法介绍 使用流之前,必须先对函数式接口.lambda表达式和方法引用有一些了解,如果您不具备这方面知识,请移驾lambda表达

java8 stream流操作

Stream 在对流进行处理时,不同的流操作以级联的方式形成处理流水线.一个流水线由一个源(source),0 到多个中间操作(intermediate operation)和一个终结操作(terminal operation)完成. 源:源是流中元素的来源.Java 提供了很多内置的源,包括数组.集合.生成函数和 I/O 通道等. 中间操作:中间操作在一个流上进行操作,返回结果是一个新的流.这些操作是延迟执行的. 终结操作:终结操作遍历流来产生一个结果或是副作用.在一个流上执行终结操作之后,该

Java8 Stream流方法

流是Java API的新成员,它允许以声明性方式处理数据集合(通过查询语句来表达,而不是临时编写一个实现).就现在来说,可以把它们看成遍历数据集的高级迭代器.此外,流还可以透明地并行处理,无需写任何多线程代码了! 流的使用一般包括三件事: •一个数据源(如集合)来执行一个查询: •一个中间操作链,形成一条流的流水线: •一个终端操作,执行流水线,并能生成结果. 流方法 含义 示例 filter (中间操作)该操作会接受一个谓词(一个返回boolean的函数)作为参数,并返回一个包括所有符合谓词的

Java8之流Stream使用,附上例子

package stream; import java.util.*; import java.util.concurrent.CopyOnWriteArrayList; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; public class steamMain { public static void main(String[] arg

关于Java8 Stream流的利与弊 Java初学者,大神勿喷

1:第一个队伍只要名字为3个字成员的姓名,存储到新集合 2:第一个队伍筛选之后只要前3人:存储到一个新集合 3:第2个队伍只要姓张的成员姓名:存储到一个新集合 4:第2个队伍不要前2人,存储到一个新集合 5:将2个队伍合并到一个队伍,存储到一个新集合 6:根据姓名创建Person对象,存储到一个新集合 //使用普通方法进行筛选 前面加了一个时间类 import java.util.ArrayList;public class Demo08List { public static void mai