• Java8核心新特性
    • Stream流
    • Optional
    • Lambda匿名函数
    • 默认方法

本篇重点介绍Stream

Stream流

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

  • 元素序列:流提供接口,可以访问特定元素类型的一组有序值,流的目的在于表达计算
  • 源:流会使用一个提供数据的源,如集合、数组或输入/输出资源
  • 数据处理操作:提供类似于数据库的操作,以及函数式编程语言中的常用操作(filter、map、reduce、find、match、sort等),串行或并行均支持
  • 并行:很多流操作本身会返回一个流,这样多个操作就可以链接起来,形成一个流水线(可以进行延迟和短路等优化)
  • 内部迭代:流的迭代操作是在后台进行的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 返回低热量菜肴的名称,并按照卡路里排序
* parallelStream():得到一个并行流
* filter:过滤
* map:将元素转换成其他形式或提取信息
* limit:阶段流,使其元素不超过给定数量
* sort:排序
* collect:将流转换为其他形式
*/
List<String> lowCaloricDishesName =
menu.parallelStream()
.filter(d -> d.getCalories() < 400)
.sorted(comparing(Dishes::getCalories))
.map(Dish::getName)
.collect(toList());

优点

  • 声明性
    • 简洁易读
    • 想要完成什么,而不是说明如何实现一个操作(利用循环和if条件等控制流语句)
  • 可复合:灵活
  • 可并行:性能更好

缺点

  • debug时需要将将语句切换为多行(例如以.换行)才可以打断点

  • 只能遍历一次

    • s.forEach第二次调用会报java.lang.IllegalStateException: stream has already been operated upon or closed异常

与集合对比

  • 存储
    • 集合:内存中的数据结构,包含所有值
    • 流:概念上固定的(不能添加或删除)
  • 计算时机
    • 集合:元素需先计算出来才能成为集合的一部分
    • 流:按需计算,只有在有需要时才会计算值
  • 创建
    • 集合:急切创建,先装满再处理
    • 流:延迟创建,需要时再计算值
  • 迭代
    • 集合:外部迭代(for-each)
    • 流:内部迭代(透明并行、优化处理顺序)

操作

  • 中间操作
    • 可以连接起来的流操作
      • filtermaplimit
  • 终端操作
    • 结果为不是流的值

使用


筛选和切片

筛选(distinct)

1
2
3
4
5
6
//2、4
List<Integer> list = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
list.stream()
.filter(integer -> integer % 2 == 0)
.distinct()
.forEach(System.out::println);

截短流limit

1
2
3
4
5
6
//2    
List<Integer> list = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
list.stream()
.filter(integer -> integer % 2 == 0)
.limit(1)
.forEach(System.out::println);

跳过skip

1
2
3
4
5
6
//4        
List<Integer> list = Arrays.asList(2, 2, 4, 1, 1, 3, 3);
list.stream()
.filter(integer -> integer % 2 == 0)
.skip(2)
.forEach(System.out::println);

映射

map

创建一个新版本

1
2
3
List<String> dishNames = menu.stream()
.map(Dish::getName)
.collect(toList());

flatmap

将结果合并为一个流

1
2
3
4
5
6
List<String> uniqueCharacters =
words.stream()
.map(w -> w.split(""))
.flatMap(Arrays::stream)
.distinct()
.collect(Collectors.toList());
查找和匹配
  • 短路
    • allMatch:流中的元素是否都能匹配
    • anyMatch:流中的元素是否有一个元素能匹配
    • noneMatch:确保流中没有任何元素匹配
  • findFirst:返回第一个匹配的值(并行流上限制多)
  • findAny:返回任意匹配的值(并行流上限制少)
规约

元素求和

1
2
3
4
5
6
// reduce(初始值,BinaryOperator<T>来将两个元素结合起来产生一个新值)
int sum = numbers.stream().reduce(0, (a, b) -> a + b);
// 代码更简洁
int sum = numbers.stream().reduce(0, Integer::sum);
// Optional用于流中没有元素的情况
Optional<Integer> sum = numbers.stream().reduce((a, b) -> (a + b));

最大值

1
numbers.stream().reduce(0, Integer::max);

最小值

1
numbers.stream().reduce(0, Integer::min);

流类型

数值流、多种来源流(文件、数组)、无限流

数值流

避免暗含的装箱成本

  • IntStreammapToIntOptionalInt
  • DoubleStreammapToDoubleOptionalDouble
  • LongStreammapToLongOptionalLong

数值流转换为一般流可用boxed()方法

1
Stream<Integer> stream = intStream.boxed();

数值范围

  • 可用于IntStreamLongStream
    • range:不包含结束值
    • rangeClosed:包含结束值
多种来源流(文件、数组)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 创建流
Stream<String> stream = Stream.of("Java", "Lambdas");

// 创建空流
Stream<String> stream = Stream.empty();

// 数组创建流
int[] numbers = {1,2,3,4,5};
int sum = Arrays.stream(numbers).sum();

// 由文件创建流
long uniqueWords = 0;
try (Stream<String> lines = Files.lines(Paths.get("data.txt"), Charset.defaultCharset())){
uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(""))).distinct().count();
} catch (IOException e) {
}
无限流
  • Stream.iterate
    • 需要依次生成一系列值
      • 斐波那契数列
    • 对象无可变状态

应该使用limit(n)来对流加以限制,防止一直计算

1
2
3
Stream.iterate(0, n -> n + 2)
.limit(10)
.forEach(System.out::println);
  • Stream.generate
    • 按需生成
    • 对象有可变的状态
1
2
3
Stream.generate(Math::random)
.limit(5)
.forEach(System.out::println);

收集数据

区分
  • Collection
    • Java集合的祖先接口
  • Collections
    • java.util包下的工具类
    • 内置各种处理集合的静态方法
  • Collector
    • 接口
    • 创建常见常见收集器实例
      • Collectors.counting():计数
      • Collectors.maxBy():求最大
      • Collectors.minBy():求最小
      • Collectors.summingInt():汇总
      • Collectors.averaginInt():求平均值
      • Collectors.joining():拼接字符串
      • Collectors.reducing(startValue, lambda, BinaryOperator):规约
      • Collectors.groupingBy(lambda):分组
      • Collectors.partitioningBy(lambda):分区
    • 功能
      • 将流元素规约和汇总为一个值
      • 元素分组
      • 元素分区
    • Collectors.toList()
  • collect
    • java.util.stream.Stream#collec
    • 负责收集流
    • 适合表达可变容器上的规约
  • Collections
    • 集合类的工具类
      • 排序
      • 搜索

  • Optional
    • isPresent():包含值的时候返回true,否则返回false
    • get():在值存在时返回,否则抛出一个NoSuchElement异常
    • orElse(T other):会在值存在时返回,否则返回一个默认值

  • 处理流的方式分为惰性求值和及早求值,对流的处理通常包括一系列的惰性求值和一个及早求值,流遇到及早求值时,才会真正的遍历和执行
  • 函数式编程时,函数应该是没有副作用的,对象不应该被修改,如果需要修改对象,则采用map方法