Java 8 流

什么是流

流(Stream)是Java 8 引入的一个新的API,其为集合的处理带来了许多便利。你可以把流看过一个管道(或是流水线),当数据经过该管道(或是流水线)时,会对数据依次进行处理,最终得到想要的结果。而且,流还可以透明地并行处理数据。《Java 8》中对流的定义是

从支持数据处理操作的源生成的元素序列

流和集合的区别

  • 集合中的元素是存在内存中的。集合对数据进行处理前是要先把所有的元素取完,而流是你计算的时候需要什么才取什么。
  • 流并不会改变原始数据源。
  • 流只能遍历一次。遍历完之后,这个流已经被消费掉了。如果还需遍历流,则可以从原始数据源那里重新获取一个新的流来遍历。
  • 集合使用的是外部迭代,而流使用的是内部迭代。也就是说流的内部已经做了迭代,我们只需要告诉流应该对数据执行什么样的操作就可以。

为什么要使用流

一句话——可以编写更简单,更易让人阅读的代码。

流操作

流包括两个操作:中间操作和终端操作。中间操作会返回另一个流,而终端操作返回非Stream值。比如void,Integer等。常用的中间操作和终端操作如下:

中间操作

操作描述
filter过滤
map映射
flatMap对流进行扁平化处理
limit截断
sorted排序
distinct去重
skip跳过元素

终端操作

操作描述返回类型
forEach消费流中的每个元素并对其应用lambdavoid
count返回流中元素个数long
collect把流归约成一个集合List、Map或Integer等
anyMatch检查流中是否又任一元素匹配给定的函数boolean
allMatch检查流中的元素是否全部匹配给定的函数boolean
noneMatch确保流中没有元素匹配给定的函数boolean
findAny返回当前流中的任意元素Optional<T>
findFirst查找第一个元素Optional<T>
reduce归约(将流归约成一个值):用于元素求和,最大值和最小值int或Optional<Integer>

注:Optional类( java.util.Optional)是一个容器类,代表一个值存在或不存在。可以利用Optional,避免和null检查相关的bug。

使用流

使用流,一般包括3个步骤:

  • 一个数据源(如集合)来执行一个查询
  • 一个中间操作链 ,形成一条流的流水线
  • 一个终端操作,执行流水线,并能生成结果

下面给出流的部分操作示例:

  public class Dish {

    private final String name;
    private final boolean vegetarian;
    private final int calories;
    private final Type type;

    public String getName() {
        return name;
    }

    public boolean isVegetarian() {
        return vegetarian;
    }

    public int getCalories() {
        return calories;
    }

    public Type getType() {
        return type;
    }

    public Dish(String name, boolean vegetarian, int calories, Type type) {
        this.name = name;
        this.vegetarian = vegetarian;
        this.calories = calories;
        this.type = type;
    }

    @Override
    public String toString() {
        return name;
    }

    public enum Type {
        MEAT, FISH, OTHER
    }
    public enum CaloricLevel { DIET, NORMAL, FAT }

    public static void main(String[] args) {
        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) );
                     
        //筛选出所有素菜
        List<Dish> vegetarianMenu = menu.stream()
                .filter(Dish::isVegetarian)  //过滤
                .collect(toList());

        //筛选出不同元素
        List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
        numbers.stream()
                .filter(i -> i % 2 == 0)
                .distinct()  //去重
                .forEach(System.out::println);

        //选出热量超过300卡路里的头三道菜
        List<Dish> dishes = menu.stream()
                .filter(d -> d.getCalories() > 300)
                .limit(3)  //截断
                .collect(toList());

        //提取流中菜肴的名称
        List<String> dishNames = menu.stream()
                .map(Dish::getName)  //映射:对流中的每一个元素应用函数
                .collect(toList());
                }
  }

归约操作:
归约操作可以用来表达更复杂的查询,比如“计算菜单中的总卡路里”或“菜单中卡路里最高的是哪个菜”。此类查询需要将流中所有元素反复结合起来得到一个值。举例如下:

List<Integer> number = Arrays.asList(1, 2, 3, 4);
//对number中所有元素求和
int sum = numbers.stream().reduce(0, (a, b) -> a + b);  
//对number中所有元素求和【更简单的写法】
int sum = numbers.stream().reduce(0, Integer::sum);
//将number中所有元素相乘
int product = numbers.stream().reduce(1, (a, b) -> a * b);
//例3:考虑流中没有任何元素的情况。reduce操作无法返回其和,因为它没有初始值。因此把结果包裹在一个Optional对象里,以表明和可能不存在。
Optional<Integer> sum = numbers.stream().reduce((a, b) -> (a + b));

说明:
reduce操作接受两个参数:

  • 一个初始值
  • 一个BinaryOperator来将两个元素结合起来产生一个新值。
  • reduce还有一个重载的变体,它不接受初始值,但是会返回一个Optional对象。如上述的例3

数值流

看了前面对reduce操作的介绍,现在用它来计算菜单的热量

int calories = menu.stream()
.map(Dish::getCalories)
.reduce(0, Integer::sum);

这段代码有一个问题,它有一个暗含的装箱成本。每个Integer都必须拆箱成一个原始类型,再进行求和。

为了解决该问题,Java 8引入了数值流,IntStream、 DoubleStream和LongStream,分别将流中的元素特化为int、 long和double,从而避免了暗含的装箱成本。需要注意的是,这些特化的原因并不在于流的复杂性,而是装箱造成的复杂性——即类似int和Integer之间的效率差异。
将流转换为特化版本的常用方法是mapToInt、 mapToDouble和mapToLong。这些方法和前面说的map方法的工作方式一样,只是它们返回的是一个特化流,而不是Stream。现在采用数值流来计算菜单的热量

int calories = menu.stream()
.mapToInt(Dish::getCalories)
.sum();

数值流和对象流之间的相互转换

IntStream intStream = menu.stream().mapToInt(Dish::getCalories);
Stream<Integer> stream = intStream.boxed();

用流收集数据

前面提到过流的终端操作会消耗流,从而产生一个最终结果。可以发现collect是一个归约操作,就像reduce一样可以接受各种做法作为参数,将流中的元素归约成一个结果。那么collect方法接受什么样的参数呢?具体的做法是通过定义新的Collector(收集器)接口来定义的

收集器

什么时候使用收集器

如上所述,要把流中所有的项目合并成一个结果时就可以用。这个结果可以是任何类型,可以复杂如list或map,或是简单如一个整数。Java 8 的Collectors 类提供了许多静态工厂方法生成新的收集器。例如,前面把流合并成一个list就用到了收集器的静态工厂方法toList()

Collectors类的静态工厂方法

方法名描述
Collectors.maxBy计算流中的最大值
Collectors.minBy计算流中的最小值
Collectors.summingInt对Int函数求和(Long型和Double型也有对应方法)
Collectors.averagingInt计算平均数(Long型和Double型也有对应方法)
Collectors.summarizingInt通过一次调用可得到最大值和最小值,以及计算其总和平均值(用IntSummaryStatistics类的getter方法来访问结果)
Collectors.counting()计算流中的元素个数
Collectors.joining连接字符串
Collectors.groupingBy对流进行分组
Collectors.reducing用于定义广泛的归约过程

事实上,上述大多收集器,都是一个可以用reducing工厂方法定义的归约过程的特殊情况而已。比如counting收集器就是类似地利用三参数reducing工厂方法实现的,参见加源码如下:

public static <T> Collector<T, ?, Long> counting() {
        return reducing(0L, e -> 1L, Long::sum);
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值