前言

刚上大学的时候,JDK1.8就已经发布了,老师口口声声说应该激进些上1.8,可是等我快要工作的时候才发现1.8中Stream与λ表达式表达式的妙用。记录一下帮助记忆。

Stream接口

java.util.Stream 表示能应用在一组元素上一次执行的操作序列。Stream 操作分为中间操作或者最终操作两种,最终操作返回一特定类型的计算结果,而中间操作返回Stream本身,这样你就可以将多个操作依次串起来。 Stream 的创建需要指定一个数据源,比如 java.util.Collection的子类:List、Set,但不支持Map。Stream的操作可以串行执行(stream)或者并行执行(parallelStream)。串行Stream上的操作是在一个线程中依次完成并行Streams,而并行Stream则是在多个线程上同时执行。

创建

由集合创建

  1. 可以通过 Collection.stream() 或者 Collection.parallelStream() 来创建Stream
  2. // 为集合创建串行流
  3. public static<T> Stream<T> stream()
  4. // 为集合创建并行流
  5. public static<T> Stream<T> parallelStream()

由数组创建

  1. // Java8 中的 Arrays 的静态方法 stream() 可以获取数组流。
  2. static <T> Stream<T> stream(T[] array)
  3. // 重载形式,能够处理对应基本类型的数组
  4. public static IntStream stream(int[] array)
  5. public static LongStream stream(long[] array)
  6. public static DoubleStream stream(double[] array)

由值创建流

  1. // 可以使用静态方法 Stream.of(), 通过显示值创建一个流。它可以接收任意数量的参数。
  2. public static<T> Stream<T> of(T... values)

由函数创建流

  1. // 创建无限流
  2. // 可以使用静态方法 Stream.iterate() 和 Stream.generate(), 创建无限流。
  3. // 迭代
  4. public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
  5. // 生成
  6. public static<T> Stream<T> generate(Supplier<T> s)

中间操作(返回的还是Stream)

中间操作,所以我们可以在过滤后的结果来应用其他Stream操作,主要方法包括:map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered。

filter 过滤

过滤通过一个predicate接口来过滤并只保留符合条件的元素。

sort 排序

排序是一个中间操作,返回的是排序好后的Stream。如果你不指定一个自定义的Comparator则会使用默认排序。
需要注意的是,排序只创建了一个排列好后的Stream,而不会影响原有的数据源,排序之后原数据是不会被修改的。

map 映射(mapToInt, flatMap 等)

中间操作map会将元素根据指定的Function接口来依次将元素转成另外的对象,下面的示例展示了将字符串转换为大写字符串。你也可以通过map来讲对象转换成其他类型,map返回的Stream类型是根据你map传递进去的函数的返回值决定的。

distinct(去重)

对于流中的基本类型和引用对象类型都可以进行去重。
但值得注意的是引用对象的去重,引用对象要实现hashCode和equal方法,否则去重无效。

match 匹配

Stream提供了多种匹配操作,允许检测指定的Predicate是否匹配整个Stream。所有的匹配操作都是最终操作,并返回一个boolean类型的值。anyMatch、noneMatch。

终止操作

此后不可再执行其他Stream操作,主要方法包括:forEach、 forEachOrdered、 toArray、 reduce、 collect、 min。

count 计数

计数是一个最终操作,返回Stream中元素的个数,返回值类型是long。

reduce 聚合

这是一个最终操作,允许通过指定的函数来讲stream中的多个元素聚合为一个元素,聚合后的结果是通过Optional接口表示的。

forEach(遍历)

forEach需要一个函数来对过滤后的元素依次执行。forEach是一个最终操作,所以我们不能在forEach之后来执行其他Stream操作。

image.png

Map的Stream新特性

前面提到过,Map类型不支持stream,不过Map提供了一些新的有用的方法来处理一些日常任务。

  1. Map map = new HashMap<>(16);
  2. for (int i = 0; i < 10; i++) {
  3. map.putIfAbsent(i, "i" + i);
  4. }
  5. map.forEach((id, val) -> System.out.println(val));

以上代码很容易理解, putIfAbsent 不需要我们做额外的存在性检查,而forEach则接收一个Consumer接口来对map里的每一个键值对进行操作。

下面的例子展示了map上的其他有用的函数:

  1. map.computeIfPresent(3, (num, val) -> (String) val + num);
  2. map.get(3); // i33
  3. map.computeIfPresent(9, (num, val) -> null);
  4. map.containsKey(9); // false
  5. map.computeIfAbsent(23, num -> num);
  6. map.containsKey(23); // true
  7. map.computeIfAbsent(3, num -> "has");
  8. map.get(3); // i33

接下来展示如何在Map里删除一个键值全都匹配的项

  1. map.remove(3, "i33");
  2. map.get(3); // i3
  3. map.remove(3, "i33");
  4. map.get(3); // null

获得键值对,如果没有则使用设置的值

  1. map.getOrDefault(42, "not found"); // not found

Merge做的事情是如果键名不存在则插入,否则则对原键对应的值做合并操作并重新插入到map中。

  1. map.merge(9, "i9", (value, newValue) -> value.concat(newValue));
  2. map.get(9); // i9
  3. map.merge(9, "concat", (value, newValue) -> value.concat(newValue));
  4. map.get(9); // i9concat

API

JDK1.8 谷歌翻译版
Interface Stream T.png

应用场景分析

##获取List中元素的某个属性的集合

  1. //把userList中User的realName全部拉出来存放到realNameList中
  2. realNameList = userList.stream().map(User::getRealName()).collect(Collectors.toList());

Array->List

方法 Arrays.asList(arrays),注意这里的List不可操作,这里的ArrayList是内部是一个内部实现类,并没有实现操作的方法。

  1. public static <T> List<T> asList(T... a) {
  2. return new ArrayList<>(a);
  3. }
  4. /**
  5. * @serial include
  6. */
  7. private static class ArrayList<E> extends AbstractList<E>
  8. implements RandomAccess, java.io.Serializable
  9. {
  10. //
  11. }

image.png

list->map并去重

  1. //默认值的一个数据
  2. return new ArrayList<>(Collections.singleton(new NameVO()));
  3. //转换成id,对象,并去重
  4. LinkedHashMap<Integer, SysDept> deptMap = listByIds(collect).stream().collect(
  5. Collectors.toMap(SysDept::getDeptId, Function.identity(),
  6. (key1, key2) -> key2, LinkedHashMap::new));

List转Array数组(原来方式)

  1. String[] strs = (String[]) lists.toArray(new String[0]);

可以看出,原来是再toArray方法里面新建一个数组,<T> T[] toArray(T[] a);这个方法里面有个小技巧:

  • 新建的数组的size小于等于list大小的话,list中所有元素都转化为数组中元素,且大小为数组大小
  • 如果size比list元素个数大的话,则补充默认值

解决办法

  1. String[] strs = (String[]) lists.toArray(new String[lists.size()]);

Stream流的toArray方法

获得Long[]类型数组

  1. list.stream().map(Subject::getId).toArray(Long[]::new);

排序

  1. //自定义排序规则:包含大小相等条件,日期(后-->前)
  2. list.sort((o1, o2) -> o1.getTime().isBefore(o2.getTime()) ? 1 :
  3. (o1.getTime().isAfter(o2.getTime()) ? -1 : 0));

同样也可以使用stream.sorted操作进行排序。sorted默认应该是hash值排序,也可以自定义Comparator规则
如上。


其他

参考

saytime Stream API
Java8新特性-Stream API

Java8特性详解 lambda表达式 Stream