Java 8 Stream API 学习总结

本人参考这篇文章(传送门》》》)学习了java 8 Stream API的使用,以下内容为一些笔记整理。


java8 中的 Stream API 使用详解

简介

Stream 是java 8的一大亮点,第一眼会把它当成IO 流,其实不然。Java 8 Stream是对集合对象(Collection)的功能增强,为集合对象提供非常便利的聚合操作,或者大批量数据操作。Stream API也采用了Lambda表达式写法,大大提高了编程效率和程序可读性。

Stream 概览

对Stream的认识

它不是集合元素,不是数据结构,可以把它理解为一个高级版本的Iterator。

Iterator -> 显式的一个个遍历元素,并附加一些操作。
Stream -> 首先给出需要执行的操作,然后Stream会隐式地在内部进行遍历,进行数据操作。

Stream 可以并行化操作,当使用并行去处理时,数据会被分成多个段,每个段在不同的线程中进行处理,然后结果一并输出。Stream 的并行操作依赖于Java 7的Fork/Join 框架来拆分任务和加速处理过程。

Java 并行API演变历程:
1. 1.0-1.4 中的 java.lang.Thread
2. 5.0 中的 java.util.concurrent
3. 6.0 中的 Phasers
4. 7.0 中的 Fork/Join 框架
5. 8.0 中的 Lambda

流的操作类型

流的操作类型分为两种:

  • Intermediate : 打开流、进行相关操作、返回一个新的流给到下个操作使用。这类操作是惰性的(Lazy),仅仅是调用了此方法,底层并未开始真正的遍历。
  • Terminal : 一个流只能有一个Terminal操作,当这个操作执行后,流就被“用光了”,无法在操作,所以这必然是流最后一个操作。Terminal操作时,底层才真正开始流的遍历,并返回一个结果,或者一个side effect。
  • 还有一个操作被称作 short-circuiting . 当操作一个无限大的 Stream , 而又希望在有限时间内完成操作,则在管道内拥有一个 short-circuiting 操作是必要非充分条件。
int sum = peopleList.stream()
    .filter(p -> p.getSex() == GIRL)
    .mapToInt(p -> p.getAge())
    .sum();

/** 这段代码的意思:
*   求人物列表中所有女孩的年龄之和:
*   构造人stream -> 过滤出女孩 -> 转换成年龄stream -> 求和
*
*   1. peopleList.stream()通过List构造一个流
*   2. filter() 过滤操作、mapToInt() 转换操作 为 Intermediate 操作类型
*   3. sum() 求和操作 为 Terminal 操作类型
*   4. 当执行sum()时,底层才真正开始遍历,进行过滤、转换等操作
*/

流的使用详解

流的构造与转换

构造流的常用方法
// 1. 使用初始值
Stream stream = Stream.of(1, 2, 3);

// 2. 使用数组
Int[] numbers = new Int[]{1, 2, 3};
stream = Stream.of(numbers);
stream = Arrays.stream(numbers);

// 3. 使用集合
List<Integer> list = Arrays.asList(numbers);
stream = list.stream();
流转换为其它数据结构
// 1. to 数据类型
Stream stream = Stream.of("ni","hao","ya");
String[] strs = stream.toArray();

// 2. to 集合
List<String> list = stream.collect(Collectors.toList());
Set set = stream.collect(Collectors.toSet());

// 3. to 字符串
String str = stream.collect(Collectors.joining()).toString();

流的操作

常见操作归类:

  • Intermediate :
    map(map、 flatMap、 mapToInt等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered
  • Terminal :
    forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator
  • Short-circuiting :
    anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit
map 与 flatMap 的使用

先来看看map。

// 举个栗子:整数list -> 平方数list
List<Integer> nums = Arrays.asList(1, 2, 3, 4);
List<Integer> squareNums = nums.stream().
map(n -> n * n).
collect(Collectors.toList());

map 生成 1:1 映射,将每个输入元素,按照规则转换成另一个元素。

再来看看flatMap。
以下例子引用别人的讲解:传送门》》》》》

案例:对给定单词列表 [“Hello”,”World”],你想返回列表[“H”,”e”,”l”,”o”,”W”,”r”,”d”]

// 1. 使用map
    String[] words = new String[]{"Hello","World"};
    List<String[]> a = Arrays.stream(words)
            .map(word -> word.split(""))
            .distinct()
            .collect(toList());
    a.forEach(System.out::print);

image

// 2. 使用flatMap
    String[] words = new String[]{"Hello","World"};
    List<String> a = Arrays.stream(words)
            .map(word -> word.split(""))
            .flatMap(Arrays::stream)
            .distinct()
            .collect(toList());
    a.forEach(System.out::print);

filter

对 stream 中的元素按一定条件进行过滤,剩余元素生成一个新的stream。

// 挑出正数
    int[] numbers = new Int[]{-1,2,0,3,-4,-5};
    int[] pNumbers = 
        Arrays.stream(numbers)
            .filter(n -> n > 0)
            .toArray();
forEach

forEach 方法接收一个lambda表达式,然后在stream的每个元素上执行该表达式。

// 1. 打印集合
    int[] numbers = new Int[]{-1,2,0,3,-4,-5};
    Arrays.stream(numbers)
        .forEach(System.out::println);

// 2. 每个元素 +1
    int[] numbers = new Int[]{-1,2,0,3,-4,-5};
    Arrays.stream(numbers)
        .forEach(n -> n=n+1);

注意:forEach 是 Terminal 操作,它执行过后,stream 就已经被“用光了”,无法再进行操作。

在 Intermediate 操作中,peek也具有相似的功能。

findFirst 方法 及 Optional 类型

这是一个 termimal 兼 short-circuiting 操作,它总是返回 Stream 的第一个元素,或者空。

它的返回值类型:Optional。作为一个容器,它可能含有某值,或者不包含。使用它的目的是尽可能避免 NullPointerException。

// Optional 两个用例
String strA = " abcd ", strB = null;
print(strA);
print("");
print(strB);
getLength(strA);
getLength("");
getLength(strB);
public static void print(String text) {
 // Java 8
 Optional.ofNullable(text).ifPresent(System.out::println);
 // Pre-Java 8
 if (text != null) {
 System.out.println(text);
 }
 }
public static int getLength(String text) {
 // Java 8
return Optional.ofNullable(text).map(String::length).orElse(-1);
 // Pre-Java 8
// return if (text != null) ? text.length() : -1;
 };

Stream 中的 findAny、max/min、reduce 等方法等返回 Optional 值。还有例如 IntStream.average() 返回 OptionalDouble 等等。

reduce

将 stream 元素组合起来。它提供一个起始值,和stream的第1,第2…第n个元素进行组合。从这个意义上说,字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce。

// 1. 字符串连接 contact="ABCD"
String contact = Stream.of("A","B","C","D")
    .reduce("",String::concat);

// 2. 求最小值 minValue=-3.0
double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0)
    .reduce(Double.MAX_VALUE,Double::min);

// 3. 求和 sumValue = 10 , 有起始值
int sumValue = Stream.of(1, 2, 3, 4)
    .reduce(0,Integer::sum);

// 4. 求和 sumValue = 10 , 无起始值
// 无起始值的reduce,由于stream可能没有元素,所以返回的是 Optional。
int sumValue = Stream.of(1, 2, 3, 4)
    .reduce(Integer::sum)
    .get();
limit & skip

limit 返回 Stream 的前面 n 个元素;skip 则是扔掉前 n 个元素(它是由一个叫 subStream 的方法改名而来)。

sorted

对 stream 进行排序。它比数组的排序更强之处在于你可以首先对 Stream 进行各类 map、filter、limit、skip 甚至 distinct 来减少元素数量后,再排序,这能帮助程序明显缩短执行时间。

Integer[] numbers = new Integer[]{5,1,4, 2, 3};
        Arrays.stream(numbers)
                .sorted((a, b) -> b-a)
                .forEach(System.out::println);
output:
5
4
3
2
1
min & max & distinct
// 1. 找出最长一行的长度
BufferedReader br = new BufferedReader(new FileReader("c:\\SUService.log"));
int longest = br.lines().
 mapToInt(String::length).
 max().
 getAsInt();
br.close();
System.out.println(longest);

// 2. 找出全文的单词,转小写,并排序
List<String> words = br.lines().
 flatMap(line -> Stream.of(line.split(" "))).
 filter(word -> word.length() > 0).
 map(String::toLowerCase).
 distinct().
 sorted().
 collect(Collectors.toList());
br.close();
System.out.println(words);
Match

Stream 有三个 match 方法:

  • allMatch : stream 全部元素符合传入的predicate,返回true
  • anyMatch : Stream 中只要有一个元素符合传入的 predicate,返回 true
  • noneMatch : Stream 中没有一个元素符合传入的 predicate,返回 true
List<Person> persons = new ArrayList();
persons.add(new Person(1, "name" + 1, 10));
persons.add(new Person(2, "name" + 2, 21));
persons.add(new Person(3, "name" + 3, 34));
persons.add(new Person(4, "name" + 4, 6));
persons.add(new Person(5, "name" + 5, 55));

boolean isAllAdult = persons.stream().
    allMatch(p -> p.getAge() > 18);
System.out.println("All are adult? " + isAllAdult);

boolean isThereAnyChild = persons.stream().
    anyMatch(p -> p.getAge() < 12);
System.out.println("Any child? " + isThereAnyChild);

output:
All are adult? false
Any child? true

进阶

自己生成流
Stream.generate
// 1. 生成10个随机数
Random seed = new Random();
Supplier<Integer> random = seed::nextInt;
Stream.generate(random).limit(10).forEach(System.out::println);

// 2. 自实现 Supplier
Stream.generate(new PersonSupplier())
    .limit(10)
    .forEach(p -> System.out.println(p.getName() + ", " + p.getAge()));
private class PersonSupplier implements Supplier<Person> {
     private int index = 0;
     private Random random = new Random();
     @Override
     public Person get() {
         return new Person(index++, "StormTestUser" + index, random.nextInt(100));
     }
}
Stream.iterator

iterate 跟 reduce 操作很像

生成的流 : 1 2 3 4 5
Stream.iterator(1, n -> n+1).limit(5)
Collectors类

java.util.stream.Collectors 类的主要作用就是辅助进行各类有用的 reduction 操作,例如转变输出为 Collection,把 Stream 元素进行归组。

groupingBy/partitioningBy
Map<Integer, List<Person>> personGroups = Stream.generate(new PersonSupplier())
    .limit(100)
    .collect(Collectors.groupingBy(Person::getAge));
Iterator it = personGroups.entrySet().iterator();
while (it.hasNext()) {
    Map.Entry<Integer, List<Person>> persons = (Map.Entry) it.next();
    System.out.println("Age " + persons.getKey() + " = " + persons.getValue().size());
}

// 首先生成 100 人的信息,然后按照年龄归组,相同年龄的人放到同一个 list 中

output :
Age 0 = 2
Age 1 = 2
Age 5 = 2
Age 8 = 1
Age 9 = 1
Age 11 = 2
……
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值