流,用声明性的方式处理数据集 - 读《Java 8实战》
引入流
Stream API的代码
- 声明性 更简洁,更易读
- 可复合 更灵活
- 可并行 性能更好
流是什么?
- 它允许以声明方式处理数据集合
- 遍历数据集的高级迭代器
- 透明地并行处理
- 简短定义:从支持数据处理操作的源生成的元素序列
- 特点:流水线和内部迭代
返回低热量菜肴的名称
List<Dish> menu = FakeDb.getMenu();
List<String> lowCaloricDishesName =
// menu.stream()
menu.parallelStream() // 并行处理
.filter(d -> d.getCalories() < 400) // 筛选低卡路里
.sorted(comparing(Dish::getCalories)) // 按卡路里排序
.map(Dish::getName) // 提取菜肴名称
.collect(toList()); // 返回list
System.out.println(lowCaloricDishesName);
流与集合?
- 流只能遍历一次
- 流是内部迭代,集合是外部迭代
流的操作?
流的使用:一个数据源,一个中间操作链,一个终端操作
流的操作有两类:中间操作与终端操作
使用流
筛选、切片
- 谓词筛选 filter
- 去重 distinct
- 截短流 limit
- 跳过元素 skip
// 筛选前2个素菜
FakeDb.getMenu().stream()
.filter(d -> d.isVegetarian()) // 这就是谓词筛选
.limit(2) // 截断流,返回前n个元素
.forEach(System.out::println);
Arrays.asList(1, 2, 2, 3, 3, 3).stream()
.distinct() // 去重
.skip(1) // 跳过元素
.forEach(System.out::println);
映射
- 对每个元素应用函数 map
- 扁平化 flatMap 把一个流中的每个值都换成另一个流,然后把所有的流连接起来成为一个流
// 提取菜名
FakeDb.getMenu().stream()
.map(Dish::getName)
.forEach(System.out::println);
// 求每个字符串长度
Arrays.asList("Java 8", "Lambdas", "In", "Action").stream()
.map(String::length)
.forEach(System.out::println);
流的扁平化 flatMap
单词列表返回字符列表
// 单词列表返回字符列表 如输入列表["Hello", "World"],输出列表["H", "e", "l", "o", "W", "r", "d"]
// 尝试一:不成功
Arrays.asList("Hello", "World").stream()
.map(w -> w.split("")) // 将每个单词转为字符数组 Stream<String[]>
.distinct()
.forEach(System.out::println);
Arrays.stream()可以将数组转换成流
// 尝试二:不成功
Arrays.asList("Hello", "World").stream()
.map(w -> w.split("")) // 将每个单词转为字符数组 Stream<String[]>
.map(Arrays::stream) // 将每个数组转换成一个单独的流 Stream<Stream<String>>
.distinct()
.forEach(System.out::println);
使用flatMap
Arrays.asList("Hello", "World").stream()
.map(w -> w.split("")) // 将每个单词转为字符数组
.flatMap(Arrays::stream)// 将各个生成流扁平化为单个流 Stream<String>
// 各个数组并不是分别映射成一个流,而是映射成流的内容
.distinct()
.forEach(System.out::println);
flatMap方法让你先把一个流中的每个值都换成另一个流,然后把所有的流连接起来成为一个流
flatMap使用举例
// 数字列表的平方列表
Arrays.asList(1, 2, 3, 4, 5, 6).stream()
.map(n -> n * n)
.forEach(System.out::println);
System.out.println("----------------------");
// 两个数组列表的数对
List<Integer> numbers1 = Arrays.asList(1, 2, 3);
List<Integer> numbers2 = Arrays.asList(3, 4);
numbers1.stream()
.flatMap(i -> numbers2.stream().map(j -> new int[]{i, j}))
.forEach(a -> System.out.println(a[0] + "\t" + a[1]));
System.out.println("----------------------");
// 总和能被3整除的数对
numbers1.stream()
.flatMap(i -> numbers2.stream()
.filter(j -> (i + j) % 3 == 0)
.map(j -> new int[]{i, j}))
.forEach(a -> System.out.println(a[0] + "\t" + a[1]));
查找和匹配
从单词列表中返回所用到的字符
allMatch/anyMatch/noneMatch/findFirst/findAny
匹配
// 至少匹配一个元素
boolean match = FakeDb.getMenu().stream()
.anyMatch(Dish::isVegetarian);
System.out.println(match);
// 匹配所有元素
match = FakeDb.getMenu().stream()
.allMatch(d -> d.getCalories() > 1000);
System.out.println(match);
// 所有都不匹配
match = FakeDb.getMenu().stream()
.noneMatch(d -> d.getCalories() > 1000);
System.out.println(match);
查找
Optional<Dish> dish = FakeDb.getMenu().stream()
.filter(Dish::isVegetarian)
.findAny();
// 关于返回值 Optional<T> 的用法:
System.out.println(dish.isPresent()); // 存在返回true,否则返回false
dish.ifPresent(System.out::println); // 存在就执行代码块
System.out.println(dish.get()); // 存在返回值,不存在抛异常
Dish defaultDish = new Dish("pork", false, 800, Dish.Type.MEAT);
System.out.println(dish.orElse(defaultDish)); // 存在返回值,否则返回默认值
// 查找第一个元素
Optional<Dish> dish2 = FakeDb.getMenu().stream()
.filter(Dish::isVegetarian)
.findFirst();
归约
// 终端操作的返回值
// allMatch boolean
// forEach void
// findAny Optional<T>
// collect R eg:List<T>
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 元素求和,有初始值的情况
int sum = numbers.stream()
.reduce(0, Integer::sum);
System.out.println(sum);
// 元素求和,无初始值
Optional<Integer> sum2 = numbers.stream()
.reduce(Integer::sum);
System.out.println(sum2.get());
// 最大值
int maxValue = numbers.stream().reduce(0, Integer::max);
System.out.println(maxValue);
// 最小值
int minValue = numbers.stream().reduce(0, Integer::min);
System.out.println(minValue);
// 菜单中菜的个数
int dishCount = FakeDb.getMenu().stream()
.map(d -> 1)
.reduce(0, (a, b) -> a + b);
System.out.println(dishCount);
// 内置count
System.out.println(FakeDb.getMenu().stream().count());
无状态操作:诸如map或filter等操作会从输入流中获取每一个元素,并在输出流中得到0或1个结果。
有状态操作:从流中排序和删除重复项时都需要知道先前的历史
归约应用举例
List<Transaction> tList = FakeDb.getTransactions();
// (1) 找出2011年发生的所有交易,并按交易额排序(从低到高)。
List<Transaction> list01 = tList.stream()
.filter(t -> t.getYear() == 2011) // 2011年的交易
.sorted(Comparator.comparing(Transaction::getValue)) // 交易额从低到高
.collect(Collectors.toList());
System.out.println(list01);
// (2) 交易员都在哪些不同的城市工作过?
List<String> cityList = tList.stream()
.map(t -> t.getTrader().getCity()) // 交易员所在城市
.distinct() // 去重
.collect(Collectors.toList());
System.out.println(cityList);
// (3) 查找所有来自于剑桥的交易员,并按姓名排序。
List<Trader> list3 = tList.stream()
.map(Transaction::getTrader) // 交易员
.filter(t -> "Cambridge".equals(t.getCity())) // 来自剑桥
.distinct() // 去重
.sorted(Comparator.comparing(Trader::getName)) // 按姓名排序
.collect(Collectors.toList());
System.out.println(list3);
// (4) 返回所有交易员的姓名字符串,按字母顺序排序。
String nameStr = tList.stream()
.map(t -> t.getTrader().getName()) // 交易员的姓名
.distinct() // 去重
// .sorted(Comparator.comparing(String::toString)) // 排序
.sorted() // 排序,可以简写
// .collect(Collectors.toList());
// .reduce("", (n1, n2) -> n1 + n2 + " "); // 这种写法效率不高
.collect(Collectors.joining(" "));
System.out.println(nameStr);
// (5) 有没有交易员是在米兰工作的?
// Optional<String> optional5 = tList.stream()
// .map(Transaction::getTrader)
// .map(Trader::getCity)
// .filter(city -> "Milan".equals(city))
// .findAny();
// System.out.println(optional5.isPresent());
boolean milanBased = tList.stream()
.anyMatch(t -> t.getTrader().getCity().equals("Milan"));
System.out.println(milanBased);
// (6) 打印生活在剑桥的交易员的所有交易额。
// Optional<Integer> optional6 = tList.stream()
// .filter(t -> "Cambridge".equals(t.getTrader().getCity()))
// .map(Transaction::getValue)
// .reduce((a, b) -> a + b);
// System.out.println(optional6.get());
tList.stream()
.filter(t -> "Cambridge".equals(t.getTrader().getCity()))
.map(Transaction::getValue)
.forEach(System.out::println);
// (7) 所有交易中,最高的交易额是多少?
Optional<Integer> optional7 = tList.stream()
.map(Transaction::getValue)
.reduce(Integer::max);
System.out.println(optional7.get());
// (8) 找到交易额最小的交易。
Optional<Integer> optional8 = tList.stream()
.map(Transaction::getValue)
.reduce(Integer::min);
System.out.println(optional8.get());
数值流与对象流
// 计算菜单中的卡路里
int calories = FakeDb.getMenu().stream()
.map(Dish::getCalories)
.reduce(0, Integer::sum);
// 它有一个暗含的装箱成本,每个Integer都必须拆箱成一个原始类型,再进行求和
System.out.println(calories);
// 原始类型流特化
// IntStream,DoubleStream,LongStream分别将流中的元素特化为int,long,double,从而避免暗含的装箱成本
// 映射到数值流
int sum = FakeDb.getMenu().stream() // 返回 Stream<Dish>
.mapToInt(Dish::getCalories) // 返回 IntStream
.sum(); // sum,max,min,average
// sum,如果流是空的,sum默认返回0
System.out.println(sum);
// 转换回对象流
FakeDb.getMenu().stream()
.mapToInt(Dish::getCalories)
.boxed(); // 转换为Stream<Integer>
// 默认值OptionalInt OptionalDouble OptionalLong
// sum有默认值0,max,min,average没有默认值
OptionalInt maxCalories = FakeDb.getMenu().stream()
.mapToInt(Dish::getCalories)
.max();
System.out.println(sum);
数值范围
// [1, 100)
IntStream numbers = IntStream.range(1, 100);
// [1 100]
IntStream numbers2 = IntStream.rangeClosed(1, 100);
// 求勾股数
IntStream.rangeClosed(1, 100).boxed()
.flatMap(a -> IntStream.rangeClosed(a, 100)
.filter(b -> Math.sqrt(a*a + b*b )%1==0)
.mapToObj(b -> new int[]{a, b, (int)Math.sqrt(a * a + b * b)}))
.forEach(a -> System.out.println(a[0] + ", " + a[1] + ", " + a[2]));
// 求勾股数 优化
IntStream.rangeClosed(1, 100).boxed()
.flatMap(a -> IntStream.rangeClosed(a, 100)
.mapToObj(b -> new double[]{a, b, Math.sqrt(a * a + b * b)}))
.filter(t -> t[2] % 1 == 0)
.forEach(a -> System.out.println(a[0] + ", " + a[1] + ", " + a[2]));
生成流
// 由值创建流
Stream.of("java 8", "Lambdas", "In", "Action")
.map(String::toUpperCase)
.forEach(System.out::println);
// 由数组创建流
int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers).sum();
System.out.println(sum);
// 由文件生成流:统计文件中的不同单词数
long uniqueWords = 0;
try(Stream<String> lines = Files.lines(Paths.get("data.txt"), Charset.defaultCharset())){
uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" ")))
.distinct()
.count();
} catch (IOException e) {
e.printStackTrace();
};
System.out.println(uniqueWords);
// 由函数生成流 无限流
// Stream.iterate
// Stream.generate
Stream.iterate(0, n -> n + 2)
.limit(10)
.forEach(System.out::println);
// 迭代:斐波纳契元组数列
Stream.iterate(new int[]{0, 1}, t -> new int[]{t[1], t[0] + t[1]})
.limit(20)
.forEach(t -> System.out.println("(" + t[0] + ", " + t[1] + ")"));
// 生成
Stream.generate(Math::random)
.limit(5)
.forEach(System.out::println);
// 生成一个全是1的无限流
IntStream.generate(() -> 1).limit(11).forEach(System.out::println);
// 生成:斐波纳契元组数列
IntStream.generate(new IntSupplier() {
private int previous = 0;
private int current = 1;
@Override
public int getAsInt() {
int oldPrevious = this.previous;
int nextValue = this.previous + this.current;
this.previous = this.current;
this.current = nextValue;
return oldPrevious;
}
}).limit(11).forEach(System.out::println);
其他,备注
遍历数组取索引值
https://www.zhihu.com/question/51841706
遍历数组取索引值
// import static java.util.stream.Collectors.toList;
String[] names = {"Sam", "Pamela", "Dave", "Pascal", "Erik"};
IntStream.range(0, names.length)
.filter(i -> names[i].length() <= i)
.mapToObj(i -> names[i])
.collect(toList()); // List<String>
// 方案一:
IntStream.range(0, names.length)
.forEach(i -> System.out.println(i + ", " + names[i]));
// 方案二:
AtomicInteger index = new AtomicInteger();
List<String> list = Arrays.stream(names)
.filter(n -> n.length() <= index.incrementAndGet())
.collect(Collectors.toList());
// 方案三:使用Guava
Streams.mapWithIndex(Arrays.stream(names)
,(str, index) -> index + ", " + str)
.forEach(System.out::println);
// 方案四:未看懂
Seq.seq(Stream.of(names)).zipWithIndex()
.filter( namesWithIndex -> namesWithIndex.v1.length() <= namesWithIndex.v2 + 1)
.toList();
// 方案五:未看懂
LazyFutureStream.of(names)
.zipWithIndex()
.filter( namesWithIndex -> namesWithIndex.v1.length() <= namesWithIndex.v2 + 1)
.toList();
// 方案六:
int[] idx = {-1};
Arrays.stream(names)
.forEach(i -> System.out.println(++idx[0] + ", " + names[idx[0]]));
第三方库
Guava
Apache
lambdaj
Joda-Time
流,用声明性的方式处理数据集 - 读《Java 8实战》的更多相关文章
- 读书笔记,《Java 8实战》,第四章,引入流
集合是Java中使用最多的API,但集合操作却远远算不上完美.主要表现在两点, 第一,集合不能让我们像数据库的SQL语言一样用申明式的语言指定操作: 第二,现在的集合API无法让我们 ...
- KBMMW SampleService/SampleClient方式传输数据集
马上周末了,趁着下午这会儿回顾一下这几天对旧项目的升级过程,一些重要但不常用的东西记录下来是很有必要的.其中一个项目中对KBMMW的远程数据通讯方式做了改进,利用SampleService/Sampl ...
- BeanFactory 使用控制反转 (IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。面向切面 将声明性事务管理集成到应用程序中
Spring 系列: Spring 框架简介 https://www.ibm.com/developerworks/cn/java/wa-spring1/ Spring 框架简介 Spring AOP ...
- 声明对象的方式/构造函数/原型/this指向
函数的发展历程(声明函数的方式): 1.通过Object构造函数或字面量的方式创建单个对象 var obj = new Object; obj.name="新华"; o ...
- 瀑布流的三种实现方式(原生js+jquery+css3)
前言 项目需求要弄个瀑布流的页面,用的是waterfall这个插件,感觉还是可以的,项目赶就没自己的动手写.最近闲来没事,就自己写个.大致思路理清楚,还是挺好实现的... 原生javascript版 ...
- javascript两种声明函数的方式的一次深入解析
声明函数的方式 javascript有两种声明函数的方式,一个是函数表达式定义函数,也就是我们说的匿名函数方式,一个是函数语句定义函数,下面看代码: /*方式一*/ var FUNCTION_NAME ...
- 数据库事务隔离级别+Spring 声明性事务隔离级别
数据库事务隔离级别 数据库提供了四种事务隔离级别, 不同的隔离级别采用不同的锁类开来实现. 在四种隔离级别中, Serializable的级别最高, Read Uncommited级别最低. 大多数数 ...
- 【Java8新特性】面试官问我:Java8中创建Stream流有哪几种方式?
写在前面 先说点题外话:不少读者工作几年后,仍然在使用Java7之前版本的方法,对于Java8版本的新特性,甚至是Java7的新特性几乎没有接触过.真心想对这些读者说:你真的需要了解下Java8甚至以 ...
- CUDA 7 Stream流简化并发性
CUDA 7 Stream流简化并发性 异构计算是指高效地使用系统中的所有处理器,包括 CPU 和 GPU .为此,应用程序必须在多个处理器上并发执行函数. CUDA 应用程序通过在 streams ...
随机推荐
- BFC理解
Block formatting context (块级格式化上下文) 页面文档由块block构成 每个block在页面上占据自己的位置 使用新的元素构建BFC overflow:hidden | a ...
- css两栏布局、圣杯布局、双飞翼布局
最近几个月一直用vue在写手机端的项目,主要写业务逻辑,在js方面投入的时间和精力也比较多.这两天写页面明显感觉css布局方面的知识有不足,所以复习一下布局方法. 两栏布局 1.浮动 .box1 .l ...
- 【二次元的CSS】—— 纯CSS3做的能换挡的电扇
这次分享的电扇,和以往用css3画人物相比 多加了一点交互,就是电扇开关的地方,用到了一点点css3的 :checked +div 这个很少用到的选择器来实现的. GitHub传送门:https:// ...
- A Beginner’s Introduction to CSS Animation中文版
现在越来越多的网站正在使用动画,无论是以GIF,SVG,WebGL,背景视频等形式. 当正确使用时,网络上的动画带来生机和交互性,为用户增添了额外的反馈和体验. 在本教程中,我将向您介绍CSS动画; ...
- C#复杂XML反序列化为实体对象两种方式
前言 今天主要讲的是如何把通过接口获取到的Xml数据转换成(反序列化)我们想要的实体对象,当然Xml反序列化和Json反序列化的方式基本上都是大同小异.都是我们事先定义好对应的对应的Xml实体模型,不 ...
- Spring Security的使用
spring security使用目的:验证,授权,攻击防护. 原理:创建大量的filter和interceptor来进行请求的验证和拦截,以此来达到安全的效果. Spring Security主要包 ...
- Node自动重启工具 nodemon
为什么要使用 在编写调试Node.js项目,修改代码后,需要频繁的手动close掉,然后再重新启动,非常繁琐.现在,我们可以使用nodemon这个工具,它的作用是监听代码文件的变动,当代码改变之后,自 ...
- 轻量化安装 TKEStack:让已有 K8s 集群拥有企业级容器云平台的能力
关于我们 更多关于云原生的案例和知识,可关注同名[腾讯云原生]公众号~ 福利: ①公众号后台回复[手册],可获得<腾讯云原生路线图手册>&<腾讯云原生最佳实践>~ ②公 ...
- vim 下几种比较省劲的方式(vi结合着用)
Vim的几种模式 正常模式:可以使用快捷键命令,或按:输入命令行. 插入模式:可以输入文本,在正常模式下,按i.a.o等都可以进入插入模式. 可视模式:正常模式下按v可以进入可视模式, 在可视模式下, ...
- 怎么快速找出帝国CMS数据库配置文件路径及迁移网站后修改技巧!
首先,我们要了解一下帝国CMS整个目录结构,只有了解清楚结构,我们才有可能快速找到自己想要的文件,比如:帝国CMS数据库配置文件路径! 帝国CMS目录结构介绍 / 系统根目录├d/ 附件和数据存放目录 ...