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结合起来可以极大的优化我们的代码, 本文只是一点简单的案例, 此后还需我们进一步的深入了解和练习.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值