Java Stream简介

关于Stream

流是java8中加入的api,他是一种处理数据的方式,在这之前我们通常都用集合来处理以及存储数据,在给出流这一api之后,集合和流之间有什么关系或者有什么不同呢?

实例对比

首先通过一个例子可以了解这两者之间的关系,这里我使用的是《Java8实战》这本书中的例子:

有这样一个菜单,上面记录了各种菜的信息。现在我们想通过一系列操作选出热量低于400的三种菜的名称并按照卡路里进行排序,它的一些详细的代码如下:

  • 形容菜的类Dish

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<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));
  • 使用Java8之前的方式获取结果

        //使用集合中的方法获取信息
        //选出卡路里低于400的对象
        List<Dish> lowCaloricDishes = new ArrayList<>();
        for (Dish d:menu) {
            if (d.getCalories() < 400){
                lowCaloricDishes.add(d);
            }
        }

        //对对象按照卡路里进行排序
        Collections.sort(lowCaloricDishes,new Comparator<Dish>(){

            @Override
            public int compare(Dish o1, Dish o2) {
                return Integer.compare(o1.getCalories(),o2.getCalories());
            }
        });

        //获取每一个对象的name
        List<String> lowCaloricDishesName = new ArrayList<>();
        for (Dish e:lowCaloricDishes) {
            lowCaloricDishesName.add(e.getName());
        }

        //控制台输出看一下
        System.out.println(lowCaloricDishesName);
  • 运行结果

[season fruit, prawns, rice]
  • 更新Java8之后的方式获取结果

        //使用流解决问题
        List<String> lowCaloricDishesName = menu.stream()
                .filter(d -> d.getCalories() < 400)
                .sorted(comparing(Dish::getCalories))
                .map(Dish::getName)
                .collect(toList());

        //控制台输出看一下
        System.out.println(lowCaloricDishesName);
  • 运行结果

[season fruit, prawns, rice]

Stream与集合的对比

通过上面一个简单的例子我们可以很容易发现,流的使用会大大简化我们筛选目标的代码。而对于流与集合之间有哪些不同,我做了一下简单的总结:

部分缓存与全部下载

我用一个图解释一下这两者之间的区别:

Stream部分缓存与全部下载
集合中的每一次操作都伴随着一个确切可操作的集合诞生
而流操作不需要每一步操作都产生一个集合

但是流操作中

这就好比我们在视频网站上看视频
Java8之前的方式就像是把整个视频下下来之后才能播放
而Java8之后引入流之后就像是把视频先缓存一部分就开始播放,不用全部下载。

流使用中需要注意的地方

  • 1.流数据操作方法分类

我们观察之前的流的代码

        //使用流解决问题
        List<String> lowCaloricDishesName = menu.stream()
                .filter(d -> d.getCalories() < 400)
                .sorted(comparing(Dish::getCalories))
                .map(Dish::getName)
                .collect(toList());

        //控制台输出看一下
        System.out.println(lowCaloricDishesName);

流的方法之所以可以连起来,是因为有一些方法输入的参数和输出的参数都是流,就使得整个语句可以连起来
通过一些简单了解我们可以知道,流的方法分为两种,一种是中间操作一种是终端操作

  • 中间操作
        //使用流解决问题
        List<String> lowCaloricDishesName = menu.stream()
                .filter(d -> {
                    System.out.println("filtering " + d.getName());
                    return d.getCalories() < 400;
                })
                .map(d -> {
                    System.out.println("mapping " + d);
                    return d.getName();
                })
                .limit(2)
                .collect(toList());

        //控制台输出看一下
        System.out.println(lowCaloricDishesName);

对比之前的流操作 换了一个limit操作,这个操作会限制最终得到结果的数量。我们可以先看看结果

filtering pork
filtering beef
filtering chicken
filtering french fries
filtering rice
mapping rice
filtering season fruit
mapping season fruit
[rice, season fruit]

这个limit操作拥有短路特性,如果遇见满足最终输出条件的数据会提前截止语句的运行
并且很容易发现,filter语句遇见满足条件的数据时,会接着运行map语句
这是流中间操作的一些特性

  • 终端操作

与中间操作不同,终端操作往往是最后一个语句,比如上面的collect方法就是终端操作
这种操作的特点在于,输入参数是流输出参数为不是流的任何值,作为流语句的结束

  • 2.流数据的使用次数

流数据只能使用一次,如果进行了终端操作之后在使用这个流会报错:

        Stream<Dish> s = menu.stream();
        s.forEach(System.out::println);//68行
        s.forEach(System.out::println);//69行

结果:

pork
beef
chicken
french fries
rice
season fruit
pizza
prawns
salmon
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
	at java.base/java.util.stream.AbstractPipeline.sourceStageSpliterator(AbstractPipeline.java:279)
	at java.base/java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:658)
	at Test.main(Test.java:69)
  • 3.迭代方式

迭代方式分为外部迭代内部迭代
for-each与迭代器都是属于外部迭代,这种迭代方式需要获取每一个元素,并执行操作,而对比流操作中的内部迭代,它不用获取每一个元素只用写出需要执行的操作就行了。

这样的代码会更简洁并且并行性更好。
内部迭代与外部迭代

本篇博客的例子内容都是从《Java8实战》中找到的。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值