JAVA 8新特性中的 Stream API

一、JAVA 中的 Stream

1.什么是 Stream ?

Stream API是JDK8中引入新特性,Stream是一个来自数据源的元素序列并支持聚合操作。可以让你以一种声明的方式处理数据,Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。

2.Stream 特点

  • 元素:是特定类型的对象,形成一个序列。 Java中的Stream并不会存储元素,而是按需计算。
  • 数据源:流的来源可以是集合,数组,I/O channel等。
  • 过滤、聚合、排序等操作:类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。
  • Pipelining(流水线/管道): 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
  • 内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式。
使用Collection接口需要用户去做迭代(比如用for-each),这称为外部迭代。 
相反,Streams库使用内部迭代——它帮你把迭代做了,还把得到的流值存在了某个地方,你只要给出一个函数说要干什么就可以了。
下面的代码列表说明了这种区别。
1.for-each 循环外部迭代
List<String> names = new ArrayList<>(); 
for(Dish d: menu){
    names.add(d.getName());
}

2.用背后的迭代器做外部迭代
 List<String> names = new ArrayList<>();
 Iterator<String> iterator = menu.iterator();
 while(iterator.hasNext()) {
     Dish d = iterator.next();
     names.add(d.getName());
 }
 
3.:内部迭代(用getName方法参数化map,提取菜名)
List<String> names = menu.stream()
             .map(Dish::getName)
             .collect(toList());
  • 只能遍历一次:数据流的从一头获取数据源,在流水线上依次对元素进行操作,当元素通过流水线,便无法再对其进行操作。遍历完之后,我们就说这个流已经被消费掉了。你可以从原始数据源那里再获得一个新的流来重新遍历一遍,就像迭代器一样(这里假设它是集合之类的可重复的源,如果是I/O通道就没戏了)。例如,以下代码会抛出一个异常,说流已被消费掉了:
public class Test {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("THE", "JAVA", "8", "OF", "STREAM", "API");
        Stream<String> stream = list.stream();
        stream.forEach(System.out::println);
        stream.forEach(System.out::println);
    }
}

THE
JAVA
8
OF
STREAM
API
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
	at java.util.stream.AbstractPipeline.sourceStageSpliterator(AbstractPipeline.java:279)
	at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)
	at com.boe.ig.bd.entity.Vo.TestMain.main(Test.java:12)

Process finished with exit code 1

在这里插入图片描述
一个stream是由三部分组成的。数据源,零个或一个或多个中间操作,一个或零个终止操作。
中间操作是对数据的加工,注意:中间操作是lazy操作,并不会立马启动,需要等待终止操作才会执行。终止操作是stream的启动操作,只有加上终止操作,stream才会真正的开始执行。

在这里插入图片描述

3.Stream 入门案例

之前(Java 7):

public class Main {
    public static void main(String[] args) {
        List<Dish> menu = Main.getDishList();
        List<Dish> lowCaloricDishes = new ArrayList<>();
        for(Dish d: menu){
            //将卡路里小于400的菜名加入lowCaloricDishes集合中
            if(d.getCalories() < 400){
                lowCaloricDishes.add(d);
            }
        }
        //根据菜名的卡路里排序(升序)
        Collections.sort(lowCaloricDishes, new Comparator<Dish>() {
            public int compare(Dish d1, Dish d2){
                return Integer.compare(d1.getCalories(), d2.getCalories());
            }
        });
        System.out.println("lowCaloricDishes are " + lowCaloricDishes);
        List<String> lowCaloricDishesName = new ArrayList<>();
        for(Dish d: lowCaloricDishes){
            //获取低卡路里的菜名
            lowCaloricDishesName.add(d.getName());
        }
        System.out.println("lowCaloricDishesName are " + lowCaloricDishesName);
    }
    //创建菜单集合
    public static List<Dish> getDishList(){
        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) );
        return menu;
    }
}

输出结果

lowCaloricDishes are [Dish(name=season fruit, vegetarian=true, calories=120, type=OTHER), Dish(name=prawns, vegetarian=false, calories=300, type=FISH), Dish(name=rice, vegetarian=true, calories=350, type=OTHER)]
lowCaloricDishesName are [season fruit, prawns, rice]

Process finished with exit code 0

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

//菜单实体类
@Data
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 enum Type {
        MEAT, FISH, OTHER
    }
}

之后(Java 8):

List<String> lowCaloricDishesName =
        menu.stream()
        .filter(d -> d.getCalories() < 400)
        .sorted(comparing(Dish::getCalories))
        .map(Dish::getName)
        .collect(toList());
System.out.println("lowCaloricDishesName are " + lowCaloricDishesName);

输出结果

lowCaloricDishesName are [season fruit, prawns, rice]

Process finished with exit code 0

使用后者有几个显而易见的好处:
1.代码是以声明性方式写的:说明想要完成什么(筛选热量低的菜肴)而不是说明如何实现一个操作(利用循环和if条件等控制流语句)。你在前面的章节中也看到了,这种方法加上行为参数化让你可以轻松应对变化的需求:你很容易再创建一个代码版本,利用Lambda表达式来筛选高卡路里的菜肴,而用不着去复制粘贴代码。
2.你可以把几个基础操作链接起来,来表达复杂的数据处理流水线(在filter后面接上sorted、map和collect操作,如图4-1所示),同时保持代码清晰可读。filter的结果被传给了sorted方法,再传给map方法,最后传给collect方法。
在这里插入图片描述

总结一下,Java 8中的Stream API可以让你写出这样的代码:

  • 声明性——更简洁,更易读
  • 可复合——更灵活
  • 可并行——性能更好
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值