目录
这篇文章中,主要记录的是如何使用流。主要包括下面几个方面:
- 从多个源创建流
- 无限流
筛选、切片和匹配
filter
接受一个谓词(一个返回boolean类型的函数)作为参数,并返回一个包含所有符合谓词的元素的流。
List<Dish> vegetarianMenu = menu.stream().filter(Dish::isVagetarian).collect(toList());
distinct
返回一个元素各异(根据流所生成的元素的hashcode和equals方法实现)的流。
List<Integer> nums = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
nums.stream().filter(i -> i % 2 == 0).distinct().forEach(System.out::println);
limit(n)
返回一个不超过指定长度的流。
List<String> threeHighCaloricDishNames = menu.stream().filter(d -> d.getCalories() > 300).map(Dish::getName).limit(3).collect(toList());
skip(n)
返回一个扔掉了前n个元素的流。如果流中的元素不足n个,则返回一个空流。
List<Dish> dishs = menu.stream().filter(d -> d.getCalories() > 300).skip(2).collect(toList());
map
接受一个函数作为参数。这个函数会被应用到每一个元素上,并将其映射成新的元素(这里是创建一个新版本而不是修改)。
List<Integer> integers = menu.stream().map(Dish::getName).map(String::length).collect(toList());
flatmap
把一个流中的每一个值都换成另一个流,然后把所有的流连接起来成为一个流。
// 对于一张单词表,如何返回一张列表,列出里面各不相同的字符
String[] words = new String[] { "hello", "world" };
List<String> distinctWords = Arrays.asList(words).stream().map(word -> word.split("")).flatMap(Arrays::stream).distinct().collect(toList());
/**
* 给定两个数字列表,如何返回所有的数对
* 例如,给定列表[1, 2, 3]和列表[3, 4],应 该返回[(1, 3), (1, 4), (2, 3), (2, 4), (3, 3), (3, 4)]
*/
public void numberPair() {
List<Integer> numbers1 = Arrays.asList(1, 2, 3);
List<Integer> numbers2 = Arrays.asList(3,4);
List<int[]> list = numbers1.stream().flatMap(i -> numbers2.stream().map(j -> new int[] { i, j }))
.collect(toList());
System.out.println(list);
}
查找、匹配和规约
anyMatch
流中是否有一个元素能匹配给定的谓词
/**
* 菜单里面是否有素食可选择
*/
public void anyMatch() {
if (menu.stream().anyMatch(Dish::isVagetarian)) {
System.out.println("The menu is (somwhat) vagetarian friendly!!");
}
}
allMatch
流中的元素是否都能匹配给定的谓词
/**
* 所有的菜的热量都低于1000卡路里
*/
public void allMatch() {
System.out.println(menu.stream().allMatch(d -> d.getCalories() < 1000));
}
noneMatch
流中没有任何元素与给定的谓词匹配
/**
* 没有菜的热量大于等于1000
*/
public void noneMatch() {
System.out.println(menu.stream().noneMatch(d -> d.getCalories() >= 1000));
}
findAny
返回当前流中的任意元素。它可以与其他流操作结合使用
/**
* 找到一道素食菜肴
*/
public void findAny() {
menu.stream().filter(Dish::isVagetarian).findAny().ifPresent(d -> System.out.println(d.getName()));
}
findFirst
找到第一个元素
/**
* 给定一个数字列表,找出第一个平方能被3整除的数
*/
public void findFirst() {
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
}
reduce
归约操作(将流归约为一个值)
reduce接受两个参数:
- 一个初始值
- 一个BinaryOperator<T>来将两个元素结合起来产生一个新值
// 求和操作
int sum = numbers.stream().reduce(0, (a, b) -> a + b);
int sum = numbers.stream().reduce(0, Integer::sum);
// 最大值,因为没有初始值,所以返回值为Optionall<Integer>
Optional<Integer> max = numbers.stream().reduce(Integer::max);
// 最小值
Optional<Integer> min = numbers.stream().reduce(Integer::min);
// 求乘操作
int sum = numbers.stream().reduce(0, (a, b) -> a * b);
流操作:无状态和有状态
诸如map或filter等操作会从输入流中获取每一个元素,并在输出流中得到0或1个结果。这些操作一般都是无状态的:它们没有内部状态。
诸如reduce、sum、max等操作需要内部状态来累积结果。不管流中有多少元素要处理,内部状态都是有界的。
诸如sort或distinct等操作一开始和map或filter一样--都是接受一个流,再生成一个流(中间操作),但有一个关键区别。从流中排序和删除重复项时都需要知道先前的历史。例如,排序要求所有元素都放入缓冲区后才能给输出流加入一个项目,这一操作的存储要求是无界的。
使用数值范围等数值流
// 计算菜单的热量
int calories = menu.stream().map(Dish::getCalories).reduce(0, Integer::sum);
上面这段代码的问题是它有一个暗含的装箱成本。每个Integer都必须拆箱装成一个原始类型,再进行求和。
为了解决上述的问题,Java引入了三个原始类型特化流接口。IntStream、LongStream和DoubleStream,分别将流中的元素特化成int、long、double,从而避免了暗含的装箱成本。每个接口都带来了进行常用数值归约的新方法,比如对数值流求和的sum,找到最大元素的max。
映射到数值流
将流转换为特化版本的常用方法有mapToInt、mapToDouble和mapToLong。这些方法跟map的工作方式一样,只是它们返回的是一个特化流,而不是Stream<T>。
// 如果流是空的,sum返回0
int calories = menu.stream().mapToInt(Dish::getCalories).sum();
转换回对象流
要把原始流转换为一般流,可以使用boxed方法
IntStream intStream = menu.stream().mapToInt(Dish::getCalories);
// 将数值流转换为Stream
Stream<Integer> stream = intStream.boxed();
默认值OptionalInt
在求和时我们有一个默认值0。但是在其他的操作,比如计算IntStream流中的最大值,在这种情况下,如果返回0是错误的,因为我们没有办法区分没有元素的流和最大值真的是0的流。因此,在Java8中,用OptionalInt、OptionalDouble和OptionalLong来应对流元素为空的情况。
// 调用max方法,会返回一个OptionalInt
OptionalInt maxCalories = menu.stream().mapToInt(Dish::getCalories).max();
// 在没有最大值的情况下,默认提供一个默认值
int max = maxCalories.orElse(1);