流是Java API的新成员,它允许你以声明性方式处理数据集合(通过查询语句来表达,而不 是临时编写一个实现)。就现在来说,你可以把它们看成遍历数据集的高级迭代器。此外,流还可以透明地并行处理,你无需写任何多线程代码了!
举例对比
定义菜及菜单
package com.wenx.unit2.chapter4;
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
}
}
//菜单
List 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));
从菜单中筛选低热量的菜肴名称,并按照卡路里排序
传统写法
List lowCaloricDishes = new ArrayList<>();
//用累加器筛 选元素
for(Dish d: menu){
if(d.getCalories() < 400){
lowCaloricDishes.add(d);
}
}
//用匿名类对 菜肴排序
Collections.sort(lowCaloricDishes,new Comparator(){
public int compare (Dish d1, Dish d2){
return Integer.compare(d1.getCalories(), d2.getCalories());
}
});
List lowCaloricDishesName = new ArrayList<>();
//处理排序后的菜名列表
for(Dish d: lowCaloricDishes){
lowCaloricDishesName.add(d.getName());
}
java8
import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.toList;
List lowCaloricDishesName =
menu.stream()
.filter(d -> d.getCalories() < 400) //选出400卡路里 以下的菜肴
.sorted(comparing(Dish::getCalories)) //按照卡路 里排序
.map(Dish::getName) //提取菜肴的名称
.collect(toList()); //将所有名称保 存在List中
//多核并行执行
List lowCaloricDishesName =
menu.parallelStream() //开启并行
.filter(d -> d.getCalories() < 400) //选出400卡路里 以下的菜肴
.sorted(comparing(Dish::getCalories)) //按照卡路 里排序
.map(Dish::getName) //提取菜肴的名称
.collect(toList()); //将所有名称保 存在List中
说明
filter、sorted、map和collect等操作是与具体线程模型无关的高层次构件,所以 它们的内部实现可以是单线程的,也可能透明地充分利用你的多核架构!在实践中,这意味着你 用不着为了让某些数据处理任务并行而去操心线程和锁了,Stream API都替你做好了!
流的特点
定义:从支持数据处理操作的源生成的元素序列
代码
List menuNameList =
menu.stream() //建立操作流水线
.filter(dish -> dish.getCalories() >300) //首先选出高热量的菜肴
.limit(3) //只选择头三个
.map(Dish::getName) //获取菜名
.collect(toList()); //将结果保存在另一个List中
先是对menu调用stream方法,由菜单得到一个流。数据源是菜肴列表(菜 单),它给流提供一个元素序列。接下来,对流应用一系列数据处理操作:filter、map、limit 和collect。除了collect之外,所有这些操作都会返回另一个流,这样它们就可以接成一条流 水线,于是就可以看作对源的一个查询。最后,collect操作开始处理流水线,并返回结果(它 和别的操作不一样,因为它返回的不是流,在这里是一个List)。在调用collect之前,没有任 何结果产生,实际上根本就没有从menu里选择元素。
请注意,和迭代器类似,流只能遍历一次。遍历完之后,我们就说这个流已经被消费掉了。 你可以从原始数据源那里再获得一个新的流来重新遍历一遍,就像迭代器一样(这里假设它是集 合之类的可重复的源,如果是I/O通道就没戏了)。
错误示例
List title = Arrays.asList("Java8", "In", "Action");
Stream s = title.stream();
s.forEach(System.out::println);
s.forEach(System.out::println); //java.lang.IllegalStateException:流已被操作或关闭
内部迭代与外部迭代
//外部迭代
1.
List names = new ArrayList<>();
for(Dish d: menu){
names.add(d.getName());
}
2.
List names = new ArrayList<>();
Iterator iterator = menu.iterator();
while(iterator.hasNext()) {
Dish d = iterator.next();
names.add(d.getName());
}
//内部迭代 --并行
List names = menu.stream().map(Dish::getName).collect(toList());
流分为中间操作和终端操作, 可以连接起来的流操作称为中间操作,关闭流的操作称为终端操作。 使用流的三件事:
中间操作
操作 | 类型 | 返回类型 | 操作参数 | 函数描述符 |
---|---|---|---|---|
filter | 中间 | Stream |
Predicate |
T -> boolean |
map | 中间 | Stream |
Function |
T -> R |
limit | 中间 | Stream |
||
sorted | 中间 | Stream |
Comparator |
(T, T) -> int |
distinct | 中间 | Stream |
终端操作
操作 | 类型 | 目的 |
---|---|---|
forEach | 终端 | 消费流中的每个元素并对其应用 Lambda。这一操作返回 void |
count | 终端 | 返回流中元素的个数。这一操作返回 long |
collect | 终端 | 把流归约成一个集合,比如 List、Map 甚至是 Integer。 |
//过滤素菜
List vegetarianMenu = menu.stream() .filter(Dish::isVegetarian).collect(toList());
//获取偶数并去重
List numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream().filter(i -> i % 2 == 0).distinct().forEach(System.out::println);
//跳过前3个
List menuNameList =
menu.stream()
.filter(dish -> dish.getCalories() >300)
.skip(3)
.collect(toList());
//单词列表,返回单词长度
List words = Arrays.asList("Java 8", "Lambdas", "In", "Action");
List wordLengths = words.stream()
.map(String::length)
.collect(toList());
//所有满足
boolean isHealthy = menu.stream().allMatch(d ->d.getCalories() < 1000);
//所有不满足
boolean isHealthy = menu.stream().noneMatch(d ->d.getCalories() < 1000);
//任意一个满足
boolean isHealthy = menu.stream().anyMatch(d ->d.getCalories() < 1000);
//查找任意一个
Optional dish =menu.stream().filter(Dish::isVegetarian).findAny();
//查找第一个
Optional dish =menu.stream().filter(Dish::isVegetarian).findFirst();
规约
//使用 for-each 循环来对数字列表中的元素求和
int sum = 0;
for (int x : numbers) {
sum += x;
}
reduce
int sum = numbers.stream().reduce(0, (a, b) -> a + b);
int product = numbers.stream().reduce(1, (a, b) -> a * b);
Optional
最大值最小值
Optional
Optional
获取数字的个数、最小值、最大值、总和以及平均值
List primes = Arrays.asList(2, 3, 5, 7, 11, 13, 17, 19, 23, 29);
IntSummaryStatistics stats = primes.stream().mapToInt((x) -> x).summaryStatistics();
System.out.println("Highest prime number in List : " + stats.getMax());
System.out.println("Lowest prime number in List : " + stats.getMin());
System.out.println("Sum of all prime numbers : " + stats.getSum());
System.out.println("Average of all prime numbers : " + stats.getAverage());
一些示例
Optional dishOptional = menu.stream().max(comparingInt(Dish::getCalories));
IntSummaryStatistics intSummaryStatistics = menu.stream().collect(summarizingInt(Dish::getCalories));
Integer collect = menu.stream().collect(summingInt(Dish::getCalories));
String collect1 = menu.stream().map(Dish::getName).collect(joining(","));
System.out.println("collect1 = " + collect1);
Integer collect2 = menu.stream().collect(reducing(0, Dish::getCalories, (i, j) -> i + j));
Integer collect3 = menu.stream().mapToInt(Dish::getCalories).reduce(0, (i, j) -> i + j);
List collect4 = menu.stream()
.sorted(comparing(dish -> dish.getName().length(),reverseOrder())).collect(toList());
collect4.forEach(dish -> System.out.println(dish.getName()));
数值范围流
//偶数流
IntStream evenNumbers = IntStream.rangeClosed(1, 100).filter(n -> n % 2 == 0);
System.out.println(evenNumbers.count());
请注意,比较一下,如果改用 IntStream.range(1, 100) ,则结果将会是 49 个偶数,因为 range 是不包含结束值的。
构建流
Stream stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action");
stream.map(String::toUpperCase).forEach(System.out::println);
//空流
Stream emptyStream = Stream.empty();
//数组创建流
int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers).sum();
//由文件生成流 文件中有多少各不相同的词
long uniqueWords = 0;
try (Stream lines =
Files.lines(Paths.get("data.txt"), Charset.defaultCharset())) {
uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" ")))
.distinct()
.count();
} catch (IOException e) {
}
//使用 Files.lines 得到一个流,其中的每个元素都是给定文件中的一行。
//然后,你可以对 line 调用 split 方法将行拆分成单词。
//应该注意的是,你该如何使用 flatMap产生一个扁平的单词流,而不是给每一行生成一个单词流。
//最后,把 distinct 和count方法链接起来,数数流中有多少各不相同的单词。
无限流
Stream API提供了两个静态方法来从函数生成流: Stream.iterate 和 Stream.generate
Stream.iterate(0, n -> n + 2) .limit(10) .forEach(System.out::println);
Stream.generate(Math::random) .limit(5) .forEach(System.out::println);
IntStream ones = IntStream.generate(() -> 1);
分组
对菜按照类别进行分组
通常写法
//结果组
Map> dishTypeMap = new HashMap<>();
//迭代
for(Dish dish : menu) {
Dish.Type dishType = dish.getType();
//查找当前类别是否存在
List dishes = dishTypeMap.get(dishType);
if(dishes == null) {
//不存在新建分组
dishes = new ArrayList<>();
dishTypeMap.put(dishType,dishes);
continue;
}
//存在直接添加
dishes.add(dish);
}
java8
Map
按照热量进行分组
Map> dishesByCaloricLevel = menu.stream().collect(
groupingBy(dish -> {
if (dish.getCalories() <= 400) return CaloricLevel.DIET;
else if (dish.getCalories() <= 700) return
CaloricLevel.NORMAL;
else return CaloricLevel.FAT;
} ));
多级分组
//二级分组
Map>> dishesByTypeCaloricLevel =
menu.stream().collect(
groupingBy(Dish::getType,
groupingBy(dish -> {
if (dish.getCalories() <= 400) return CaloricLevel.DIET;
else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
else return CaloricLevel.FAT;
} )
)
);
分组统计
Map typesCount = menu.stream().collect(
groupingBy(Dish::getType, counting()));
分组求最大
Map> mostCaloricByType =
menu.stream()
.collect(groupingBy(Dish::getType,
maxBy(comparingInt(Dish::getCalories))));
上一个的优化版:
Map mostCaloricByType =
menu.stream()
.collect(groupingBy(Dish::getType,
collectingAndThen(
maxBy(comparingInt(Dish::getCalories)),
Optional::get)));
//收集器返回的结果转换为另一种类型,你可以使用Collectors.collectingAndThen 工厂方法返回的收集器
分组求和
Map totalCaloriesByType =
menu.stream().collect(groupingBy(Dish::getType,
summingInt(Dish::getCalories)));
分区
分区是分组的特殊情况:由一个谓词(返回一个布尔值的函数)作为分类函数,它称分区函 数。分区函数返回一个布尔值,这意味着得到的分组 Map 的键类型是 Boolean ,于是它最多可以 分为两组—— true 是一组, false 是一组。
Map> partitionedMenu =
menu.stream().collect(partitioningBy(Dish::isVegetarian));
分区后分组
Map>> vegetarianDishesByType =
menu.stream().collect(
partitioningBy(Dish::isVegetarian,
groupingBy(Dish::getType)));
分区求最大
Map mostCaloricPartitionedByVegetarian =
menu.stream().collect(
partitioningBy(Dish::isVegetarian,
collectingAndThen(
maxBy(comparingInt(Dish::getCalories)),
Optional::get)));
二级分区
menu.stream().collect(partitioningBy(Dish::isVegetarian,
partitioningBy (d -> d.getCalories() > 500)));
分区统计
menu.stream().collect(partitioningBy(Dish::isVegetarian,counting()));