Java8实战 — 引入流

原创
2016/12/27 17:21
阅读数 251

流是什么

    流是Java API的新成员,它允许你以声明的方式处理数据集合,简单来说,可以把它当作数据集的高级迭代器。此外,流还可以透明地并行处理,你无需写任何多线程代码了。

    举个例子来说明流的好处,有一个简单的场景,要求返回低热量的菜肴名称,并按照卡路里排序,实体代码如下:
 

package cn.net.bysoft.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
    }
}
package cn.net.bysoft.chapter4;

import java.util.Arrays;
import java.util.List;

public class Restaurant {
    private 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));

    public List<Dish> getMenu() {
        return menu;
    }

    public void setMenu(List<Dish> menu) {
        this.menu = menu;
    }

}

    先用Java7写一次:

        // 餐厅对象
        Restaurant restaurant = new Restaurant();

        // 返回低热量的菜肴名称,并按照卡路里排序。
        // Java7写法
        List<Dish> lowCaloricDishes = new ArrayList<>();
        for (Dish d : restaurant.getMenu()) {
            if (d.getCalories() < 400)
                lowCaloricDishes.add(d);
        }

        Collections.sort(lowCaloricDishes, new Comparator<Dish>() {

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

        });
        
        List<String> lowCaloricDishesName = new ArrayList<>();
        for(Dish d : lowCaloricDishes) 
            lowCaloricDishesName.add(d.getName());

    这段代码中,用了一个“垃圾变量”lowCaloricDishes,它唯一的作用就是作为一次性的中间容器,在Java8中,实现的细节被放在它本该归属的库中:

        // 餐厅对象
        Restaurant restaurant = new Restaurant();
        
        // Java8写法
        List<String> threeHighCaloricDishNames = restaurant.getMenu().stream()
                .filter(d -> d.getCalories() > 300)
                .sorted(comparing(Dish::getCalories))
                .map(Dish::getName)
                .limit(3)
                .collect(toList());

    利用多核架构并行执行这段代码只需要吧stream()换成parallelStream()就可以了。

    Java8中的Stream API可以让你写出的这样的代码:

  • 声明性 —— 更简洁、更易读;
  • 可复合 —— 更灵活;
  • 可并行 —— 性能更好;

流简介

    流的简短定义是:从支持数据处理操作的源生成的元素序列。

  • 元素序列 —— 流提供了一个接口,可以访问特定元素类型的一组有序值;
  • 源 —— 流会使用一个提供数据的源,如集合、数组或输入/输出资源;
  • 数据处理操作 —— 流的数据处理功能支持类似于数据库的操作,以及函数式编程语言中的常用操作;
  • 流水线 —— 很多流操作本身会返回一个流,多个操作就可以连接起来;
  • 内部迭代 —— 流的迭代操作是在背后进行的;

    从上面的代码中能够体现上面的概念:

  • filter —— 接受Lambda,从流中排除某些元素;
  • map —— 接收Lambda,将元素转换成其他形式或提取数据;
  • limit —— 截断流;
  • collect —— 将流转换为其他形式;

流与集合的差别

只能遍历一次

    流只能遍历一次,遍历完之后,就说这个流已经消费掉了。可以从原始数据源在获得一个新的流来重新遍历一边,就像迭代器一样。下面的操作会抛出一个异常:

package cn.net.bysoft.chapter4;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

public class Example2 {
    public static void main(String[] args) {
        List<String> title = Arrays.asList("Java8", "In", "Action");
        Stream<String> s = title.stream();
        s.forEach(System.out::println);
        s.forEach(System.out::println);
    }
}
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
	at java.util.stream.AbstractPipeline.sourceStageSpliterator(Unknown Source)
	at java.util.stream.ReferencePipeline$Head.forEach(Unknown Source)
	at cn.net.bysoft.chapter4.Example2.main(Example2.java:12)

外部迭代与内部迭代

    使用Collection接口需要用户去迭代,这称为外部迭代。相反,Streams库使用内部迭代——它帮你把迭代做了,还把得到的流值存在了某个地方,只要给出一个函数说要干什么就可以了。例如:

        // 餐厅对象
        Restaurant restaurant = new Restaurant();
        
        // 使用for-each循环做外部迭代
        List<String> names1 = new ArrayList<>();
        for(Dish d : restaurant.getMenu()) 
            names1.add(d.getName());
        
        // 使用迭代器做外部迭代
        List<String> names2 = new ArrayList<>();
        Iterator<Dish> iterator = restaurant.getMenu().iterator();
        while(iterator.hasNext()) {
            Dish d = iterator.next();
            names2.add(d.getName());
        }
        
        // 使用流做内部迭代
        List<String> names3 = restaurant.getMenu().stream()
                .map(Dish::getName)
                .collect(toList());

流操作

    java.util.stream.Stream中的Stream接口定义了许多操作,可以分为两大类:中间操作和终端操作。

操作 类型 返回 参数 描述
filter 中间 Stream<T> Predicate<T> T -> boolean
map 中间 Stream<R> Function<T,R> T -> R
limit 中间 Stream<T>    
sorted 中间 Stream<T> Comparator<T> (T,T) -> int
distint 中间 Stream<T>    
forEach 终端      
count 终端      
collect 终端      

    诸如filter或sorted等中间操作会返回另一个流。这让多个操作可以连接起来形成一个查询。

    终端操作会从流的流水线生成结果。

    总而言之,流的使用一般包括三件事:

  • 一个数据源来执行一个查询;
  • 一个中间操作链形成一条流水线;
  • 一个终端操作执行流水线,并生成结果;
展开阅读全文
打赏
0
0 收藏
分享
加载中
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部