少女祈祷中...

1.流的概述

a sequence of elements from source that supports aggregate operations

  • sequence of elements: 一个流对外提供一个接口,可以访问到一串特定的数据。流不存储元素,但是可以根据需要进行计算转化
  • source:数据来源,如数据结构,数组,文件等
  • aggregate operation:聚合操作,流支持像SQL操作或者其他函数式语言的操作,如filter/map/reduce/find/match/sorted等
  • pipelining: 很多流操作也是返回一个流
  • Internal Iteration: 流操作进行迭代,用户感知不到循环遍历

Stream语法

  • 类似SQL语句,遵循“做什么而非怎么做”原则

流的工作流程

  • 流的创建
  • 流的转换,将流转换为其他流的中间操作,可包括多个步骤(惰性操作)
  • 流的计算结果。这个操作会强制执行之前的惰性操作。这个步骤以后,流就再也不用了

2.流的创建

  • Collection接口的stream方法
    1
    2
    Stream<String> a1 = new ArrayList<String>().stream();
    Stream<String> a2 = new HashSet<String>().stream();
    • 还有其他的子类,如LinkedList, LinkedSet, TreeSet, Vector等
  • Arrays.stream可以将数组转为Stream
    1
    Stream<String> b1 = Arrays.stream("a,b,c,d,e".split(","), 3, 5);
  • 利用Stream类进行转化
    • of 方法,直接将数组转化
    1
    2
    3
    Stream<Integer> c1 = Stream.of(new Integer[5]);
    Stream<String> c2 = Stream.of("a,b,c".split(","));
    Stream<String> c3 = Stream.of("a", "b", "c");
    • empty方法,产生一个空流
    1
    Stream<String> d1 = Stream.empty();
  • 利用Stream类进行转化
    • generate 方法,接收一个Lambda表达式
    1
    2
    Stream<String> e1 = Stream.generate(()->"hello");
    Stream<Double> e2 = Stream.generate(Math::random);
    • iterate方法,接收一个种子,和一个Lambda表达式
    1
    Stream<BigInteger> e3 = Stream.iterate(BigInteger.ZERO, n -> n.add(BigInteger.ONE));
  • 其他类/方法产生Stream流
    • Files.lines方法
    1
    Stream<String> contents = Files.lines(Paths.get("C:/abc.txt")); 
    • Pattern的splitAsStream方法
    1
    Stream<String> words = Pattern.compile(",").splitAsStream("a,b,c");

基本类型流

  • IntStream,LongStream,DoubleStream
1
2
3
4
5
6
7
8
9
10
IntStream s1 = IntStream.of(1,2,3,4,5);

s1 = Arrays.stream(new int[] {1,2,3});
s1 = IntStream.generate(()->(int)(Math.random() * 100));
s1 = IntStream.range(1,5); //1,2,3,4 step 1
s1 = IntStream.rangeClosed(1,5); //1,2,3,4,5

IntStream s2 = IntStream.of(1,2,3,4,5);
Stream<Integer> s3 = s2.boxed();
IntStream s5 = s3.mapToInt(Integer::intValue);

并行流

  • 使得所有的中间转换操作都将被并行化
  • Collections.parallelStream()将任何集合转为并行流
  • Stream.parallel()方法,产生一个并行流
1
2
3
IntStream s1 = IntStream.range(1,10000000);
long evenNum = s1.parallel().filter(n->n%2==0).count();
System.out.println(evenNum);
  • 注意:需要保证传给并行流的操作不存在竞争

3.Stream的转换

过滤filter

  • filter(Predicate<? super T> predicate)
  • 接收一个Lambda表达式,对每个元素进行判定,符合条件留下
1
2
3
4
Stream<Integer> s1 = Stream.of(1, 2, 3, 4, 5);
Stream<Integer> s2 = s1.filter(n -> n>2);
s2.forEach(System.out::println);
//3, 4, 5

去重distinct

  • distinct()
  • 对流的元素进行过滤,去除重复,只留下不重复的元素
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//基本类型包装类对象去重
Stream<Integer> s1 = Stream.of(1, 1, 2, 2, 3, 3);
Stream<Integer> s2 = s1.distinct();
s2.forEach(System.out::println);
// 1, 2, 3

//自定义对象的去重
ArrayList<Student> students = new ArrayList<Student>();
students.add(new Student("Tom", 20));
students.add(new Student("Tom", 20));
students.add(new Student("Jerry", 20));
students.add(new Student("Jerry", 18));

// 先对象的hashCode再调用equals方法进行判重
Stream<Student> s3 = students.stream().distinct();
s3.forEach(System.out::println);

排序sorted

  • sorted()
  • 对流的基本类型包装类元素进行排序
1
2
3
4
Stream<Integer> s1 = Stream.of(3,2,4,1,5);
Stream<Integer> s2 = s1.sorted();
s2.forEach(System.out::println);
//1, 2, 3, 4, 5
  • 提供Comparator,对流的元素进行排序
1
2
3
4
5
6
7
8
String[] planets = new String[] { 
"Mercury", "Venus", "Earth",
"Mars", "Jupiter", "Saturn",
"Uranus", "Neptune" };

Stream<String> s3 = Stream.of(planets).sorted(
Comparator.comparing(String::length));
s3.forEach(System.out::println);
  • 对流的自定义对象元素进行排序,调用对象的compareTo方法
1
2
3
4
5
6
7
8
ArrayList<Cat> cats = new ArrayList<>();
cats.add(new Cat(3));
cats.add(new Cat(2));
cats.add(new Cat(5));
cats.add(new Cat(1));
cats.add(new Cat(4));
Stream<Cat> s4 = cats.stream().sorted();
s4.forEach(System.out::println);

转化map

  • 利用方法引用对流每个元素进行函数计算
1
2
3
Stream<Double> s1 = Stream.of(-1.5, 2.5, -3.5);
Stream<Double> s2 = s1.map(Math::abs);
s2.forEach(System.out::println);
  • 利用Lambda表达式对流每个元素进行函数计算
1
2
3
Stream<Integer> s3 = Stream.of(1,2,3,4,5);
Stream<Integer> s4 = s3.map(n->n*n);
s4.forEach(System.out::println);
  • 利用方法引用,对流每个元素进行函数计算返回Stream
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
String[] planets = new String[] { 
"Mercury", "Venus", "Earth"};

Stream<String> allLetters2 =
Stream.of(planets).flatMap(word -> letters(word));
allLetters2.forEach(System.out::print);
//flatMap 执行一对多的转换,然后将所有的Map都展开
//['M','e','r','c','u','r','y',
// 'V','e','n','u','s',
// 'E','a','r','t','h']

public static Stream<String> letters(String word)
{
List<String> result = new ArrayList<>();
for(int i=0;i<word.length();i++)
{
result.add(word.substring(i, i+1));
}
return result.stream();
}
  • 利用方法引用,对流每个元素进行函数计算返回Stream,并合并
1
2
3
4
5
6
Stream<Stream<String>> allLetters = 
Stream.of(planets).map(word -> letters(word));
allLetters.forEach(System.out::print);
//[['M','e','r','c','u','r','y'],
// ['V','e','n','u','s'],
// ['E','a','r','t','h']]

抽取limit

1
2
3
4
//获取前n个元素
Stream<Integer> s1 = Stream.of(1,2,3,4,5,6,7,8,9,10);
Stream<Integer> s2 = s1.limit(3);
s2.forEach(System.out::println);

跳过skip

1
2
3
4
//跳过前n个元素
Stream<Integer> s3 = Stream.of(1,2,3,4,5,6,7,8,9,10);
Stream<Integer> s4 = s3.skip(8);
s4.forEach(System.out::println);

连接concat

1
2
3
//连接两个流
Stream<String> s5 = Stream.concat(letters("hello"), letters("world"));
s5.forEach(System.out::println);

额外调试peek

1
2
3
Stream<Double> s1 = Stream.iterate(1.0, n -> n*2)
.peek(n -> System.out.println("number:" + n)).limit(5);
s1.forEach(System.out::println);

4.Optional类型

  • 一个包装器对象
  • 要么包装了类型T的对象,要么没有包装任何对象(还是null)
  • 如果T有值,那么直接返回T的对象
  • 如果T是null,那么可以返回一个替代物
1
2
3
4
5
6
7
Optional<String> s1 = Optional.of(new String("abc"));
String s2 = s1.get();
System.out.println("s2: " + s2); //abc

Optional<String> s3 = Optional.empty();
String s4 = s3.orElse("def");
System.out.println("s4: " + s4); //def

Optional<T>创建

  • of方法
  • empty方法
  • ofNullable方法,对于对象有可能为null情况下,安全创建
1
2
3
4
5
Optional<String> s5 = Optional.of(new String("abc"));
Optional<String> s6 = Optional.empty();
String s7 = null;
Optional<String> s8 = Optional.ofNullable(s7);
//s7不为Null,s8就是s7, 否则s8就为Optional.empty()

Optional<T>使用

  • get方法,获取值,不安全的用法
    -orElse方法,获取值,如果为null,采用替代物的值
  • orElseGet方法,获取值,如果为null,采用Lambda表达式值返回
  • orElseThrow方法,获取值,如果为null,抛出异常
  • ifPresent方法,判断是否为空,不为空返回true
  • isPresent(Consumer), 判断是否为空,如果不为空,则进行后续Consumer操作,如果为空,则不做任何事情
  • map(Function), 将值传递给Function函数进行计算。如果为空,则不计算

Optional<T>注意事项

  • 直接使用get,很容易引发NoSuchElementException异常
  • 使用isPresent判断值是否存在,这和判断null是一样的低效

5.流的计算结果

简单约简(聚合函数)

  • count(), 计数
1
2
Stream<Integer> s1 = Stream.of(a);
long countResult = s1.filter(n-> n>0).count();
  • max(Comparator),最大值,需要比较器
1
2
Stream<Integer> s2 = Stream.of(a);
Optional<Integer> maxResult = s2.max((n1,n2)->n1-n2);
  • min(Comparator),最小值,需要比较器
1
2
Stream<Integer> s3 = Stream.of(a);
Optional<Integer> minResult = s3.min((n1,n2)->n1-n2);
  • findFirst(), 找到第一个元素
1
2
Stream<Integer> s4 = Stream.of(a);
Optional<Integer> first = s4.findFirst();
  • findAny(), 找到任意一个元素
1
2
Stream<Integer> s5 = Stream.of(a);
Optional<Integer> random = s5.findAny();
  • anyMatch(Predicate),如有任意一个元素满足Predicate,返回true
1
2
Stream<Integer> s6 = Stream.of(a);
System.out.println(s6.anyMatch(n -> n>6));
  • allMatch(Predicate),如所有元素满足Predicate,返回true
1
2
Stream<Integer> s7 = Stream.of(a);
System.out.println(s7.allMatch(n -> n>6));
  • noneMatch(Predicate),如没有任何元素满足Predicate,返回true
1
2
Stream<Integer> s8 = Stream.of(a);
System.out.println(s8.noneMatch(n->n>100));

自定义约简

  • reduce,传递一个二元函数BinaryOperator,对流元素进行计算
  • 如求和、求积、字符串连接等
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Integer[] a = new Integer[] {2,4,6,8};

Stream<Integer> s1 = Stream.of(a);
Optional<Integer> sum = s1.reduce(Integer::sum);
System.out.println(sum.get());

Stream<Integer> s2 = Stream.of(a);
Optional<Integer> product = s2.reduce((x,y)->x*y);
System.out.println(product.get());

Stream<Integer> s3 = Stream.of(a);
Integer product3 = s3.reduce(1,(x,y)->x*y);
System.out.println(product3);

String[] b = new String[] {"abc","def","ghi"};
Stream<String> s4 = Stream.of(b);
String bigStr = s4.reduce("",(x,y)->x+y);
System.out.println(bigStr);

查看/遍历元素

  • iterator(),遍历元素
  • forEach(Consumer),应用一个函数到每个元素上
1
2
3
4
5
6
7
8
9
10
Integer[] a = new Integer[] {2,4,6,8};

Stream<Integer> s1 = Stream.of(a);
Iterator<Integer> it1 = s1.filter(n->n>2).iterator();
while(it1.hasNext()) {
System.out.println(it1.next());
}

Stream<Integer> s2 = Stream.of(a);
s2.filter(n->n>2).forEach(System.out::println);

存放到数据结构中

  • toArray(),将结果转为数组
  • collect(Collectors.toList()),将结果转为List
1
2
3
4
5
6
7
//List
Stream<Integer> s1 = Stream.of(a);
List<Integer> list1 = s1.collect(Collectors.toList());

//Linkedlist
Stream<Integer> s2 = Stream.of(a);
List<Integer> list2 = s2.collect(Collectors.toCollection(LinkedList::new));
  • collect(Collectors.toSet()),将结果转为Set
1
2
Stream<Integer> s3 = Stream.of(a);
Set<Integer> set1 = s3.collect(Collectors.toSet());
  • collect(Collectors.toMap()), 将结果转为Map
1
2
3
4
5
6
7
List<Person> persons = new ArrayList<Person>();
persons.add(new Person(1, "Jerry"));
persons.add(new Person(2, "Tom"));

//将流存储为Map
Stream<Person> s6 = persons.stream();
Map<Integer, String> map1 = s6.collect(Collectors.toMap(Person::getId, Person::getName));
  • collect(Collectors.joining()), 将结果连接起来
1
2
3
4
5
6
7
8
9
//将流变换为字符流,并连接起来
Stream<Integer> s4 = Stream.of(a);
String result = s4.map(String::valueOf).collect(Collectors.joining());
System.out.println(result); //2468

//将流变换为字符流,并连接起来
Stream<Integer> s5 = Stream.of(a);
String result2 = s5.map(String::valueOf).collect(Collectors.joining(","));
System.out.println(result2); //2,4,6,8

流的高阶计算

  • 分组groupingBy和分区partitionBy
  • 分组后的约简
    • counting
    • summing
    • maxBy
    • minBy
  • 以上方法均在java.util.stream.Collectors中

6.流的应用

Java Stream的优点

  • 统一转换元素
  • 过滤元素
  • 利用单个操作合并元素
  • 将元素序列存放到某一个集合中
  • 搜索满足某些条件的元素的序列
  • 类似SQL操作,遵循“做什么而非怎么做”原则
  • 简化了串行/并行的大批量操作

Java Stream vs 循环迭代代码

  • Stream广泛使用Lambda表达式,只能读取外围的final或者effectively final变量,循环迭代代码可以读取/修改任意的局部变量。
  • 在循环迭代代码块中,可以随意break/continue/return,或者抛出异常,而Lambda表达式无法完成这些事情。
  • Stream流不是淘汰循环迭代代码,应该是两者相互搭配使用

Stream应用注意事项

  • 一个流,一次只能一个用途,不能多个用途,用了不能再用
  • 避免创建无限流
  • 注意操作顺序
  • 谨慎使用并行流
    • 底层使用Fork-Join Pool,处理计算密集型任务
    • 数据量过小不用
    • 数据结构不容易分解的时候不用,如LinkedList等 –数据频繁拆箱装箱不用
    • 涉及findFirst或者limit的时候不用

Stream vs Collection

  • Stream和Collection两者可以互相转化
  • 如果数据可能无限,用Stream
  • 如果数据很大很大,用Stream
  • 如果调用者将使用查找/过滤/聚合等操作,用Stream
  • 当调用者使用过程中,发生数据改变,而调用者需要对数据一致性有较高要求,用Collection