一. 流的概念
流是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 porkmapping porkfiltering prawnsmapping prawnsfiltering chickenmapping 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。