一. 流的概念

    流是Java API的新成员,它允许你以声明性的方式处理数据集合(通过查询语句来表达,而不是临时编写一个实现)。

    Stream API的特性:

        -- 声明性:更简洁,更易读;

        -- 可复合:更灵活;

        -- 可并行:性能更好。


    1.流的定义从支持数据处理操作生成的元素序列

        流有两个重要特点:

            -- 流水线:很多流操作本身会返回一个流,这样多个操作就可以链接起来,形成一个大的流水线;

            -- 内部迭代:与使用迭代器显式迭代的集合不同,流的迭代操作是在背后进行的。

        案例:

public class Dish {
    private final String name;
    private final int calories;
    private final boolean vegetarian;
    private final DishType type;
    
    public Dish(String name, boolean vegetarian, int calories, DishType type) {
        this.name = name;
        this.vegetarian = vegetarian;
        this.calories = calories;
        this.type = type;
    }
    
    public String getName() {
        return name;
    }
    
    public int getCalories() {
        return calories;
    }
    
    public boolean isVegetarian() {
        return vegetarian;
    }
}
public static void main(String[] args) {
    List<Dish> menu = Arrays.asList(
        new Dish("pork", false, 800, DishType.MEAT),
        new Dish("prawns", false, 300, DishType.FISH),
        new Dish("chicken", false, 700, DishType.MEAT),
        new Dish("rice", true, 350, DishType.OTHER)
    );
    
    List<String> threeHighCaloricDishNames = menu.stream() //菜单流Stream<Dish>
        .filter(d -> d.getCalories() > 300) //Stream<Dish>
        .map(Dish::getName) //Stream<String>
        .limit(3) //Stream<String>
        .collect(toList()); //List<String>,collect操作开始处理流水线
        
    System.out.println(threeHighCaloricDishNames);  //结果:[pork, chicken, rice]
}

        数据源:menu,它给流提供一个元素序列,然后对流应用一系列数据处理操作:filter、map、limit和collect。除了collect之外,所有这些操作都会返回一个流,这样它们就可以接成一条流水线,于是就可以看成对源的一个查询。最后collect开始处理流水线,并返回结果。在调用collect之前,没有任何结果产生,实际上根本没有从menu里选择元素。可以理解为:链中的方法都在排队等待,直到调用collect。


    2.流操作

        流操作分为中间操作和终端操作。可以连接起来的流操作称为中间操作,关闭流的操作称为终端操作。

        中间操作:中间操作会返回另一个流。这让多个操作可以连接起来形成一个查询。

                        除非流水线上触发一个终端操作,否则中间操作不会执行任何处理。这是因为中间操作一般都可以合并起来,在终端操作时一次性全部处理。

        终端操作:终端操作会从流的流水线生成结果。其结果是任何不是流的值。

public static void main(String[] args) {
    List<Dish> menu = Arrays.asList(
    new Dish("pork", false, 800, DishType.MEAT),
    new Dish("prawns", false, 300, DishType.FISH),
    new Dish("chicken", false, 700, DishType.MEAT),
    new Dish("rice", true, 350, DishType.OTHER)
    );
    
    List<String> names = menu.stream()
        .filter(d -> {
            System.out.println("filtering " + d.getName());
            return d.getCalories() > 130;
        })
        .map(d -> {
            System.out.println("mapping " + d.getName());
            return d.getName();
        })
        .limit(3)
        .collect(toList());
        
    System.out.println(names);
}

输出结果:
filtering pork
mapping pork
filtering prawns
mapping prawns
filtering chicken
mapping chicken
[pork, prawns, chicken]

        根据上例可以发现,尽管filter和map是两个独立的操作,但它们合并到同一次遍历中了(这种技术称为循环合并)。


二. 流与集合的异同

    集合与流之间的差异在于什么时候进行计算。

    集合:是一个内存中的数据结构,它包含数据结构中目前所有的值--集合中的每个元素都得先算出来才能添加到集合中。

    流:是在概念上固定的数据结构(不能添加或删除元素),其元素师按需计算的。

    流就像是一个延迟创建的集合:只有在消费者要求的时候才会计算值。


    1.流只能遍历一次

        流和迭代器类似,只能遍历一次,遍历后说明这个流已经被消费了。

public static void main(String[] args) {
    List<String> title = Arrays.asList("dispatch", "settlement", "wyvern");
    Stream<String> s = title.stream();
    s.forEach(System.out::println);
    s.forEach(System.out::println); //当执行到这里时会报IllegalStateException: stream has already been operated upon or closed
}


    2.集合和流在遍历数据方式的区别

        Collection接口需要做迭代(for-each),称为外部迭代。

        Stream库使用内部迭代。

        内部迭代的优点:项目可以透明地并行处理,或者用更优化的顺序进行处理。

        内部迭代的前提:你已经预先定义好了能够隐藏迭代的操作列表,例如filter或map。