java8 stream流操作
本书类容来自O’Reilly的《Java8 实战》一书
Stream流操作是java8中的一个重要的部分, 他让我们对集合类的操作更加的简洁高效
stream 流
现在假设我们要操作一份菜单, 菜Dish类定义如下:
public class Dish {
private String name;
private boolean vegetarian;
private int calories;
private Type type;
public enum Type {
/**
* 肉
*/
MEAT,
/**
* 鱼
*/
FISH,
/**
* 其他
*/
OTHER
}
假设我们需要筛选出卡路里含量大于300的菜, 我们会这么写代码
List<Dish> result = new ArrayList<>();
for(Dish dish : menu) {
if(dish.getCalories() <300) {
result.add(dish);
}
}
以上的代码很简单, 但是代码量比较多, 在java8中, Collection加入了Stream()方法, 他会返回Stream< T>对象, 我们可以通过Stream流来进行简化
List<Dish> result = menu.filter(dish -> dish.getCalories() < 300).collect(Collectors.toList());
这段代码只有一行就达到了我们想要的结果, 而且非常直观, 但这只是Stream的冰山一角, 接下来我们将更详细学习Stream流操作.
内部迭代
在上面的例子中, 我们用传统方法遍历menu菜单, 而在用Stream没有看见列表的遍历过程, 我们称之为内部迭代, 与之对应的就是传统foreach循环的外部迭代. 在java8中, 我们除了Stream()方法之外, 还有parallelStream()方法, 这个方法返回一个并行流, 就是一个吧内容分成多个数据块, 用不同的线程分别处理每个数据块的流, 并把工作分别给CPU中所有的内核, 让他们并行工作, 所以说, 使用parallelStream()方法, 内部迭代的速度远远改与外部迭代
Stream 常用方法
上面的例子我们看到了Stream的filter()方法, 这是一个筛选器, 参数是一个带有一个参数, 返回值为布尔型的方法, , 通过他我们可以对列表中的每一个元素进行筛选, 获取我们需要的部分. 除此之外, Stream还有很多方法, 我们在这里举一个简单的例子
List<String> threeHighCaloricDishNamesList = getMenu()
// 获得流对象
.stream()
// 筛选卡路里大于300的Dish对象
.filter(dish -> dish.getCalories() > 300)
// 逆向排序
.sorted(Comparator.comparing(Dish::getCalories).reversed())
// 获取菜名
.map(Dish::getName)
// 去掉重复元素
.distinct()
// 限制前三个
.limit(3)
// 打包为list
.collect(Collectors.toList());
filter, map, collect
sorted()方法可以让我们对流中的元素通过我们想要的方法进行分类,
Comparator的comparing()方法会通过他的参数中函数的返回值进行分类, 结果为升序, 通过reversed()方法改为降序排列
distinct()方法和SQL中的distinct一样, 是去掉重复
map()方法对流中的对象进行操作, 传入的函数的意思是将原有流中原有的对象变为它的返回值, 在本例中map()将Stream< Dish>转换为了Stream< String>, 本来流中存入的是一个个Dish对象, 现在变成了String, 也就是菜的名字
limit()就是去钱n个元素, 他有一个与之互补的方法skip(), 意思是从第n个元素开始取值
collect()为元素的打包方式, 在此之前, 我们得到的一直都只是Stream对象, 而不是List对象, 通过传入参数Collectors.toList()我们可以得到List对象, 同样的, 也有Collectors.toSet()方法
match
除此之外, 我们还有allMatch(), noneMatch(), anyMatch()三个方法检测器是否存在复合条件的元素
// 是否全部符合
boolean isHeavy = menu.stream().allMatch(dish -> dish.getCalories() > 1000);
// 是否全部不符合
isHeavy = menu.stream().noneMatch(dish -> dish.getCalories() > 1000);
// 是否至少有一项符合
isHeavy = menu.stream().anyMatch(dish -> dish.getCalories() > 1000);
查找元素
我们可以通过findAny和findFirst找到复合条件的第一个元素
menu.stream()
// 筛选
.filter(Dish::isVegetarian)
// 找到所有符合要求的值, 返回值为Optional<T>
.findAny()
// 如果不为空则执行Lambda表达式
.ifPresent(System.out::println);
这两个函数返回值为Optional< T>, 而不是我们所需要的的Dish或者List, 这是因为防止我们拿到的值为null, 然后马上对其进行操作而导致异常, 我们可以通过get()方法获得对象, 在本例中用ifPresent()方法判断其是否为空, 部位空则遍历并操作
流的扁平化处理
我们有一个数组[“hello”, “world”], 我们想获得其中所有不重复的字母, 怎么办呢?
words.stream()
.map(word -> word.split(""))
.disctinc()
.collect(toList())
上面的代码其返回值不是List< String>, 而是List< String[]>, map操作后只是把两个字符串拆开成单个字母, 得到的是两个数组但是并没有合并为一个数组, 那么怎么做呢?
stream有一个flatMap()函数, 它可以将Stream扁平化
List<String> result = words.stream()
.map(word -> word.split(""))
.flatMap(Arrays::stream)
.distinct()
.collect(Collectors.toList());
该函数对Stream< String[]>每一个数组的流进行了合并, 从而得到了Stream< String>
reduce
我们可以通过reduce对元素进行求和等操作
// 求和
int sum = list.stream().reduce(0, Integer::sum);
// 求积
int mul = list.stream().reduce(1, (a, b) -> a * b);
// 最大值, 最小值和总数
Optional<Integer> max = list.stream().reduce(Integer::max);
Optional<Integer> min = list.stream().reduce(Integer::min);
其第一个操作为初值, 在求和中我们定初值为0,使每个元素都和其初值相加, 在求积中定为1, 是每一个元素与其相乘, 如果reduce只有参数的话, 其返回值就是Optional对象
Stream转换
IntStream, DoubleStream
Stream还有一些变体, IntStream, DoubleStream, 他们有一些sum(), max()等对数值进行操作的函数, 我们可以通过mapToInt()等方法得到, 同样可以通过 boxed()方法得到Stream< T>对象
IntStream intStream = menu.stream().mapToInt(Dish::getCalories);
Stream<Integer> stream = intStream.boxed();
Stream分类打包
假设我们需要对菜的类型进行分类怎么办呢? 我们可以通过Collectors.groupingBy()来按照我们想要的方式进行分类进行分类
// 根据菜的类型分类
Map<Dish.Type, List<Dish>> dishesByDishType = menu.stream().collect(Collectors.groupingBy(Dish::getType));
// 根据菜的卡路里含量登记分类
Map<String, List<Dish>> dishesByCaloriesLevel = menu.stream().collect(Collectors.groupingBy(this::caloriesLevel));
/**
* 返回菜的卡路里等级
*
* @param dish
* @return
*/
public static String caloriesLevel(Dish dish) {
int highCalories = 400;
int normalCalories = 300;
if (dish.getCalories() >= highCalories) {
return "High Calories";
} else if (dish.getCalories() >= normalCalories) {
return "Normal Calories";
} else {
return "Low Calories";
}
}
同样的, 我们可以进行二级分类:
// 二重分类
Map<Dish.Type, Map<String, List<Dish>>> dishes = menu.stream()
// 第一层分类
.collect(Collectors.groupingBy(Dish::getType,
// 第二层分类
Collectors.groupingBy(StreamCollection::caloriesLevel)));
甚至更复杂点:
// 找出每个类型中卡路里含量最大的菜
Map<Dish.Type, Dish> mostCaloriesByType = menu.stream()
// 按菜类型分组
.collect(Collectors.groupingBy(Dish::getType,
// 获取分组后每个分组中最大的菜, 封装在Optional中
Collectors.collectingAndThen(Collectors.maxBy(Comparator.comparingInt(Dish::getCalories)),
// 从Optional中获取Dish对象
Optional::get)));
函数Collectors.maxBy()的返回值是Optonal对象, Collectors.collectingAndThen()的第一个参数是Optional, 第二个参数是对Optional操作, 在本例中是获取Optional中的对象
Stream是一个很强大的工具, 将它和Lambda结合起来可以极大的优化我们的代码, 本文只是一点简单的案例, 此后还需我们进一步的深入了解和练习.