Stream笔记

前言

一、stream是什么?

支持顺序和并行聚合操作的一系列元素。

集合和流,同时具有一些表面上的相似之处,具有不同的目标。 集合主要关注其元素的有效管理和访问。 相比之下,流不提供直接访问或操纵其元素的手段,而是关心声明性地描述其源和将在该源上进行聚合的计算操作。
流管道,可以被视为流源上的查询 。 除非源是明确设计用于并发修改(例如ConcurrentHashMap ),否则在查询流源时可能会导致不可预测或错误的行为,流只能运行(调用中间或终端流操作)一次。

二、使用

数据准备:

List<Dish> menu = Arrays.asList(
new Dish("pork", false, 800, Dish.Type.MEAT),
new Dish("beef", false, 700, Dish.Type.MEAT),
new Dish("chicken", false, 400, Dish.Type.MEAT),
new Dish("french fries", true, 530, Dish.Type.OTHER),
new Dish("rice", true, 350, Dish.Type.OTHER),
new Dish("season fruit", true, 120, Dish.Type.OTHER),
new Dish("pizza", true, 550, Dish.Type.OTHER),
new Dish("prawns", false, 300, Dish.Type.FISH),
new Dish("salmon", false, 450, Dish.Type.FISH) );
public class Dish {
private final String name;
private final boolean vegetarian;
private final int calories;
private final Type type;
public Dish(String name, boolean vegetarian, int calories, Type type) {
	this.name = name;
	this.vegetarian = vegetarian;
	this.calories = calories;
	this.type = type;
}
public String getName() {
	return name;
}
public boolean isVegetarian() {
	return vegetarian;
}
public int getCalories() {
	return calories;
}
public Type getType() {
	return type;
}
@Override
public String toString() {
	return name;
}
public enum Type { MEAT, FISH, OTHER }
}

1.用谓词筛选

Streams接口支持filter方法。该操作会接受一个谓词(一个返回boolean的函数)作为参数,并返回一个包括所有符合谓词的元素的流。

List<Dish> vegetarianMenu = menu.stream().filter(Dish::isVegetarian).collect(toList());

2.筛选各异的元素

流还支持一个叫作distinct的方法,它会返回一个元素各异(根据流所生成元素的hashCode和equals方法实现)的流。例如,以下代码会筛选出列表中所有的偶数,并确保没有重复。

List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream().filter(i -> i % 2 == 0).distinct().forEach(System.out::println);

3.截短流

流支持limit(n)方法,该方法会返回一个不超过给定长度的流。所需的长度作为参数传递给limit。如果流是有序的,则最多会返回前n个元素。比如,可以建立一个List,选出热量超过300卡路里的头三道菜

List<Dish> dishes = menu.stream().filter(d -> d.getCalories() > 300).limit(3).collect(toList());

4.跳过元素

流还支持skip(n)方法,返回一个扔掉了前n个元素的流。如果流中元素不足n个,则返回一个空流。请注意, limit(n)和skip(n)是互补的!例如,下面的代码将跳过超过300卡路里的头两道菜,并返回剩下的。

List<Dish> dishes = menu.stream().filter(d -> d.getCalories() > 300).skip(2).collect(toList());

5.对流中每一个元素应用函数

流支持map方法,它会接受一个函数作为参数。这个函数会被应用到每个元素上,并将其映射成一个新的元素(使用映射一词,是因为它和转换类似,但其中的细微差别在于它是“创建一个新版本”而不是去“修改”)。例如,下面代码将要找出每道菜的名称有多长。

List<Integer> dishNameLengths = menu.stream().map(Dish::getName).map(String::length).collect(toList());

6.检查谓词是否至少匹配一个元素

使用flatMap方法的效果是,各个数组并不是分别映射成一个流,而是映射成流的内容。所有使用map(Arrays::stream)时生成的单个流都被合并起来,即扁平化为一个流。所以flatmap方法让你把一个流中的每个值都换成另一个流,然后把所有的流连接起来成为一个流。

List<String> words = Arrays.asList("Java 8", "Lambdas", "In", "Action");
List<String> uniqueCharacters = words.stream().map(w -> w.split("")).flatMap(Arrays::stream)
.distinct().collect(Collectors.toList());

7.对流中每一个元素应用函数

anyMatch方法可以回答“流中是否有一个元素能匹配给定的谓词”。比如,可以用它来看看菜单里面是否有素食可选择。

if(menu.stream().anyMatch(Dish::isVegetarian)) {
	System.out.println("The menu is (somewhat) vegetarian friendly!!");
}

8.检查谓词是否匹配所有元素

1、allMatch方法的工作原理和anyMatch类似,但它会看看流中的元素是否都能匹配给定的谓5.3 查找和匹配谓词。比如,可以用它来看看菜品是否有利健康(即所有菜的热量都低于1000卡路里):

boolean isHealthy = menu.stream().allMatch(d -> d.getCalories() < 1000);

2、和allMatch相对的是noneMatch。它可以确保流中没有任何元素与给定的谓词匹配。比如,可以用noneMatch重写前面的例子

boolean isHealthy = menu.stream().noneMatch(d -> d.getCalories() >= 1000);

9.查找元素

findAny方法将返回当前流中的任意元素。它可以与其他流操作结合使用。比如,可能想找到一道素食菜肴。可以结合使用filter和findAny方法来实现这个查询:流水线将在后台进行优化使其只需走一遍,并在利用短路找到结果时立即结束。

Optional<Dish> dish =menu.stream().filter(Dish::isVegetarian).findAny();
//因为上面代码有可能返回没有值的Optional,所以可以显式地检查Optional对象中是否存在一道菜
menu.stream().filter(Dish::isVegetarian).findAny().ifPresent(d -> System.out.println(d.getName());
ifPresent如 果 包 含 一 个值就打印它,否则什么都不做。

10.查找第一个元素

有些流有一个出现顺序( encounter order)来指定流中项目出现的逻辑顺序(比如由List或排序好的数据列生成的流)。对于这种流,你可能想要找到第一个元素。为此有一个findFirst方法,它的工作方式类似于findany。例如,给定一个数字列表,下面的代码能找出第一个平方能被3整除的数:

List<Integer> someNumbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> firstSquareDivisibleByThree =
	someNumbers.stream().map(x -> x * x).filter(x -> x % 3 == 0).findFirst(); // 9

何时使用findFirst和findAny
可能会想,为什么会同时有findFirst和findAny呢?答案是并行。找到第一个元素在并行上限制更多。如果不关心返回的元素是哪个,请使用findAny,因为它在使用并行流时限制较少。

11.元素求和

reduce接受两个参数:
1、一个初始值;
2、一个BinaryOperator来将两个元素结合起来产生一个新值,

int sum = numbers.stream().reduce(0, (a, b) -> a + b);	//求和
int product = numbers.stream().reduce(1, (a, b) -> a * b);	//求积
//当然可以用lambad表达式
int sum = numbers.stream().reduce(0, Integer::sum);
//也可以无初始值
Optional<Integer> sum = numbers.stream().reduce((a, b) -> (a + b));
Optional<Integer> max = numbers.stream().reduce(Integer::max);	//求最大值
Optional<Integer> min = numbers.stream().reduce(Integer::min);	//同理,最小值

stream常用方法

12.数值流

IntStream、 DoubleStream和LongStream,分别将流中的元素特化为int、 long和double,从而避免了暗含的装箱成本。
用刚学的reduce来计算菜单的热量,如下:

int calories = menu.stream().map(Dish::getCalories).reduce(0, Integer::sum);

但段代码的问题是,它有一个暗含的装箱成本。每个Integer都必须拆箱成一个原始类型,再进行求和。要是可以直接像下面这样调用sum方法:

int calories = menu.stream().map(Dish::getCalories).sum();

所以映射到数值流,如下,如果流是空的, sum默认返回0。

int calories = menu.stream().mapToInt(Dish::getCalories).sum();

同样,一旦有了数值流,你可能会想把它转换回非特化流。例如, IntStream上的操作只能产 生 原 始 整 数 : IntStream 的 map 操 作 接 受 的 Lambda 必 须 接 受 int 并 返 回 int ( 一 个IntUnaryOperator)。但是你可能想要生成另一类值,比如Dish。为此,你需要访问Stream接口中定义的那些更广义的操作。要把原始流转换成一般流(每个int都会装箱成一个Integer),可以使用boxed方法:

IntStream intStream = menu.stream().mapToInt(Dish::getCalories);
Stream<Integer> stream = intStream.boxed();

13.数值范围

假设你想要生成1和100之间的所有数字。 Java 8引入了两个可以用于IntStream和LongStream的静态方法,帮助生成这种范围:range和rangeClosed。这两个方法都是第一个参数接受起始值,第二个参数接受结束值。但range是不包含结束值的,而rangeClosed则包含结束值。

IntStream evenNumbers = IntStream.rangeClosed(1, 100).filter(n -> n % 2 == 0);

14.创建流

1、由值创建流:使用静态方法Stream.of,通过显式值创建一个流。它可以接受任意数量的参数。例如,以下代码直接使用Stream.of创建了一个字符串流。然后,可以将字符串转换为大写,再一个个打印出来:

Stream<String> stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action");
stream.map(String::toUpperCase).forEach(System.out::println);、
//可以使用empty得到一个空流
Stream<String> emptyStream = Stream.empty();

2、由数组创建流:可以使用静态方法Arrays.stream从数组创建一个流。它接受一个数组作为参数。例如,可以将一个原始类型int的数组转换成一个IntStream

int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers).sum();

3、由文件生成流:Java中用于处理文件等I/O操作的NIO API(非阻塞 I/O)已更新,以便利用Stream API。java.nio.file.Files中的很多静态方法都会返回一个流。例如,一个很有用的方法是Files.lines,它会返回一个由指定文件中的各行构成的字符串流。

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) {
}

4、无限流:Stream API提供了两个静态方法来从函数生成流: Stream.iterate和Stream.generate。这两个操作可以创建所谓的无限流:不像从固定集合创建的流那样有固定大小的流。由iterate和generate产生的流会用给定的函数按需创建值,因此可以无穷无尽地计算下去!一般来说,应该使用limit(n)来对这种流加以限制,以避免打印无穷多个值。

三、总结

事实上,流让可以简洁地表达复杂的数据处理查询。此外,流可以透明地并行化。

  • Streams API可以表达复杂的数据处理查询。
  • 可以使用filter、 distinct、 skip和limit对流做筛选和切片。
  • 可以使用map和flatMap提取或转换流中的元素。
  • 可 以 使 用 findFirst 和 findAny 方 法 查 找 流 中 的 元 素 。 你 可 以 用 allMatch、noneMatch和anyMatch方法让流匹配给定的谓词。
  • 这些方法都利用了短路:找到结果就立即停止计算;没有必要处理整个流。
  • 可以利用reduce方法将流中所有的元素迭代合并成一个结果,例如求和或查找最大元素。
  • filter和map等操作是无状态的,它们并不存储任何状态。 reduce等操作要存储状态才能计算出一个值。sorted和distinct等操作也要存储状态,因为它们需要把流中的所有元素缓存起来才能返回一个新的流。这种操作称为有状态操作。
  • 流有三种基本的原始类型特化: IntStream、 DoubleStream和LongStream。它们的操作也有相应的特化。
  • 流不仅可以从集合创建,也可从值、数组、文件以及iterate与generate等特定方法创建。
  • 无限流是没有固定大小的流。

参考书籍:Java8实战。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值