Java8新特性-Stream

Stream

简介

Stream 作为 Java8 的一大亮点,它与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念,Stream 是对集合的包装,通常和lambda一起使用。使用stream可以支持许多操作,如 map, filter, limit, sorted, count, min, max, sum, collect 等等。


Stream的特性

Steam主要具有一下三点特性

  • stream不存储数据
  • stream不改变数据
  • stream的延迟执行特性

通常我们在数组或集合的基础上创建Stream,Stream不会专门的存储数据,对Stream的操作也不会影响到创建它的数组或集合

对于同一个Stream的聚合、消费或收集操作只能进行一次,再次操作会报错,一个流只能发生一次操作

public static void main(String[] args) {
  int[] arr = new int[]{1, 2, 3};
  // 对同一个Stream做两次消费
  IntStream intStream = Arrays.stream(arr);
  // forEach
  intStream.forEach(System.out::println);
  // 第二次forEach发生异常
  intStream.forEach(System.out::println);
}

而对于新的流则不会发生异常

public static void main(String[] args) {
  int[] arr2 = new int[]{1, 2, 3};
  Arrays.stream(arr2).forEach(System.out::println);
  // new 正常输出
  Arrays.stream(arr2).forEach(System.out::println);
}

stream的操作是延迟执行的,在列出字符串长度大于3的例子中,在collect方法执行之前,filter、sorted、map方法还未执行,只有当collect方法执行时才会触发之前转换操作

public boolean filter(String s) {
  System.out.println("begin compare");
  return s.length() > 3;
}

@Test
public void test2() {
  List<String> strs = new ArrayList<String>() {
    {
      add("abc");
      add("abcd");
    }
  };
  Stream<String> stream = strs.stream().filter(this::filter);
  System.out.println("split-------------------------------------");
  List<String> list = stream.collect(Collectors.toList());
  System.out.println(list);
}

结果如下:

split-------------------------------------
begin compare
begin compare
[abcd]

由此可以看出,在执行完 filter时,没有实际执行filter中的方法,而是等到执行collect时才会执行,即是延迟执行。

注意:

  • 由于stream的延迟执行特性,在聚合操作执行之前修改数据源是允许的;
  • 当我们操作一个流的时候,并不会修改底层的集合(即使集合是线程安全的),若修改了原来的集合,就无法定义流操作的输出。
/**
 * 延迟执行特性,在聚合操作之前都可以添加相应元素
 */
@Test
public void test3() {
  List<String> wordList = new ArrayList<String>() {
    {
      add("a");
      add("b");
    }
  };

  Stream<String> words = wordList.stream();
  wordList.add("END");
  long n = words.distinct().count();
  System.out.println(n);
}

输出结果为 3


延迟执行特性会产生干扰

@Test
public void test4(){
  List<String> wordList = new ArrayList<String>() {
    {
      add("a");
      add("b");
    }
  };

  Stream<String> words1 = wordList.stream();
  words1.forEach(s -> {
    System.out.println("s->"+s);
    if (s.length() < 4) {
      System.out.println("select->"+s);
      wordList.remove(s);
      System.out.println(wordList);
    }
  });
}

输出结果

s->a
select->a
[b]
s->null

java.lang.NullPointerException

创建Stream

public static void main(String[] args) {
  // 1.通过集合创建流
  //Collections.singletonList("a");
  List<String> list = Arrays.asList("a", "b", "c");
  // 普通流
  Stream<String> stream1 = list.stream();
  // 并行流(多个线程处理)
  Stream<String> stream2 = list.parallelStream();

  // 2.通过数组创建流
  int[] arr = new int[]{1, 2, 3, 4};
  IntStream stream3 = Arrays.stream(arr);

  // 3.Stream.of()创建流
  Stream<Integer> integerStream = Stream.of(1, 2, 3, 4);
  integerStream.forEach(System.out::println);

  // 4.创建规律的无限流
  Stream<Integer> stream = Stream.iterate(0, x -> x + 2).limit(4);

  // 5.创建无限流
  Stream.generate(() -> "number" + new Random().nextInt()).limit(3);

  // 6.创建空流
  Stream<Object> empty = Stream.empty();
}

Stream操作分类

操作分为 中间操作 和 终结操作

  1. 中间操作分为 无状态有状态 操作
    • 无状态是指元素的处理不受之前元素的影响;
    • 有状态是指该操作只有拿到所有元素之后才能继续下去;
  2. 终结操作分为 短路非短路 操作
    • 短路是指遇到某些符合条件的元素就可以得到最终结果;
    • 非短路是指必须处理完所有元素才能得到最终结果;

我们通常还会将中间操作称为 懒操作,也正是由这种 懒操作结合终结操作数据源构成的处理管道(Pipeline)实现了 Stream 的高效

img


中间操作

无状态

/**
 * 中间操作:无状态
 * filter:过滤流,过滤流中的元素
 * map:转换流,将一种类型的流转换为另外一种类型的流
 * flapMap:拆解流,将流中的每一个元素拆解组成一个新流
 */
public class OperateStream {
  public static void main(String[] args) {
    Integer[] arr = new Integer[]{1, 2, 3, 4, 5};
    // 1.filter过滤流中的元素
    Arrays.stream(arr).filter(x -> x > 3 && x < 6).forEach(System.out::println);

    List<String> list = Arrays.asList("ADadc", "dweqdAAS");
    // 2.map转换流,将一种类型的流转换为另一种类型
    list.stream().map(x -> x.toUpperCase()).forEach(System.out::println);

    // 3.flapMap 拆解流,将流中的每一个元素拆解,组成一个新的流
    String[] str1 = {"a", "b"};
    String[] str2 = {"c", "d"};
    String[] str3 = {"e", "f"};
    Stream.of(str1, str2, str3).flatMap(Arrays::stream).forEach(System.out::println);
  }
}

有状态

/**
 * 中间操作:有状态
 * distinct 去重
 * sorted 排序
 * limit 获取前面的指定数量元素
 * skip 跳过前面指定数量的元素,获取后面的元素
 * concat 把两个stream合并成一个stream
 */
public class OperateStream2 {
  public static void main(String[] args) {
    // 1.sorted 按照字符串长度排序
    String[] str = {"abc", "a", "b", "cd", "defg"};
    System.out.println("长度排序");
    Arrays.stream(str).sorted(Comparator.comparing(String::length)).forEach(System.out::println);

    // 2.reversed() 倒序排列
    System.out.println("长度倒序");
    Arrays.stream(str).sorted(Comparator.comparing(String::length).reversed()).forEach(System.out::println);

    // Comparator.reverseOrder():也是用于翻转顺序,用于比较对象(Stream里面的类型必须是可比较的)
    // 3.按首字母倒序排列
    System.out.println("首字母倒序排序");
    Arrays.stream(str).sorted(Comparator.reverseOrder()).forEach(System.out::println);

    // 4.自然排序 按首字母的顺序
    Arrays.stream(str).sorted(Comparator.naturalOrder()).forEach(System.out::println);

    // 5.thenComparing
    // 先按首字母排序,之后按照长度排序
    System.out.println("先按首字母排序,再按照长度排序");
    Arrays.stream(str).sorted(Comparator.comparing(OperateStream2::firstChar).thenComparing(String::length)).forEach(System.out::println);

    // 6.limit 从流中获取前n个元素
    System.out.println("获取前n个元素");
    Stream.generate(() -> new Random().nextInt()).limit(3).forEach(System.out::println);

    // 7.skip跳过前n个数据
    System.out.println("skip跳过第一个元素");
    Stream.iterate(1, x -> x + 2).skip(1).limit(3).forEach(System.out::println);

    // 8.concat 把两个Stream合成一个Stream, 合并的Stream类型必须相同
    Stream<Integer> stream1 = Stream.iterate(1, x -> x + 2).limit(3);
    Stream<Integer> stream2 = Stream.iterate(1, x -> x + 2).skip(1).limit(3);

    // 9.合并
    System.out.println("去重合并");
    Stream.concat(stream1, stream2).distinct().forEach(System.out::println);
  }

  public static char firstChar(String x) {
    // 返回首字母
    return x.charAt(0);
  }
}

终结操作

非短路操作

/**
 * 终结操作:非短路操作
 * forEach:遍历
 * toArray:将流转换为Object数组
 * reduce:归约,可以将流中的元素反复结合起来,得到一个值
 * collect:收集,将流转换为其他形式,比如List、Set、Map
 * groupBy:分组
 * max:返回流的最大值,无方法参数
 * min:最小值
 * count:返回流中的元素总个数,无方法参数
 * summaryStatistics:获取汇总统计数据,比如最大值,最小值,平均值等
 */
public class OperateStream3 {
  public static void main(String[] args) {
    // 1.forEach
    List<String> list = new ArrayList<String>() {
      {
        add("a");
        add("b");
      }
    };
    list.stream().forEach(System.out::println);
    //list.forEach(System.out::println);

    // 2.reduce 归约
    Optional<Integer> optional = Stream.of(1, 2, 3).filter(x -> x > 1).reduce((x, y) -> x + y);
    System.out.println("reduce 归约");
    System.out.println(optional.get());

    List<Book> books = Arrays.asList(
      new Book(1, 22, "扬尼斯阿德托昆博", "北京"),
      new Book(1, 20, "扬尼斯阿德托昆博", "北京"),
      new Book(1, 24, "扬尼斯阿", "北京"),
      new Book(1, 25, "勒布朗詹姆斯", "天津")
    );

    // 3.collect收集,将流转换为其他形式  略。。。
    // Collectors.toList() Collectors.toSet() Collectors.toMap(k, v)
    Map<String, Book> map = books.stream().collect(Collectors.toMap(b -> b.getName(), Function.identity(), (k1, k2) -> k2));

    // 4.groupingBy 分组
    Map<String, List<Book>> map2 = books.stream().collect(Collectors.groupingBy(b -> b.getName()));
    System.out.println("分组");
    System.out.println(map2);

    // 5.partitioningBy 分组
    // 如果只有两类,使用partitioningBy 比 groupBy 效率更高
    Map<Boolean, List<Book>> map1 = books.stream().collect(Collectors.partitioningBy(x -> x.getAge() > 22));

    // 6.max、min
    String[] str = new String[]{"a", "ab", "abc", "送你一朵小红花"};
    System.out.println("max、min");
    Stream.of(str).max(Comparator.comparing(String::length)).ifPresent(System.out::println);
    Stream.of(str).min(Comparator.comparing(String::length)).ifPresent(System.out::println);

    // 7.count
    long count = Stream.of(str).count();
    System.out.println("count");
    System.out.println(count);

    // 8.summaryStatistics 获得Stream中元素的各种汇总数据
    List<Integer> number = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    IntSummaryStatistics summaryStatistics = number.stream().mapToInt(x -> x).summaryStatistics();
    System.out.println("list中最大的数字:" + summaryStatistics.getMax());
    System.out.println("list中最小的数字:" + summaryStatistics.getMin());
    System.out.println("list中所有数字总和:" + summaryStatistics.getSum());
    System.out.println("list中所有数字平均值:" + summaryStatistics.getAverage());
  }
}

短路操作

/**
 * 终结操作:短路操作
 * anyMatch:检查是否有一个元素匹配,方法参数为断言型接口
 * allMatch:检查是否匹配所有元素,方法参数为断言型接口
 * findFirst:返回第一个元素,无方法参数
 * findAny:返回当前流的任意元素,无方法参数
 * noneMatch:检查是否没有匹配所有元素,方法参数为断言型接口
 */
public class OperateStream4 {
  public static void main(String[] args) {
    String[] str = new String[]{"b", "ab", "abc", "abcd", "abcde"};

    // 1. anyMatch 检查是否有一个元素匹配
    boolean b = Stream.of(str).anyMatch(x -> x.startsWith("a"));
    System.out.println("anyMatch------");
    System.out.println(b);

    // 2.allMatch
    boolean a = Stream.of(str).allMatch(x -> x.startsWith("a"));
    System.out.println("allMatch------");
    System.out.println(a);

    // 3. noneMatch 检查是否没有匹配所有元素,只要有匹配的就返回true
    boolean a1 = Stream.of(str).noneMatch(x -> x.startsWith("a"));
    System.out.println("noneMatch------");
    System.out.println(a1);

    // 4. findFirst 返回大于3的第一个元素
    String s = Stream.of(str).parallel().filter(x -> x.length() > 3).findFirst().orElse("nothing");
    System.out.println("findFirst 返回大于3的第一个元素" + s);

    // 5. findAny 找到任意匹配的元素
    // 对并行流十分有效,只要在任何片段发现了一个匹配的元素,就会结束整个运算
    Optional<String> optional = Stream.of(str).parallel().filter(x -> x.length() > 2).findAny();
    System.out.println("findAny 找到任意匹配的元素");
    optional.ifPresent(System.out::println);
  }
}

Optional类型

/**
 * Optional类型
 * 通常聚合操作会返回一个 Optional类型,Optional表示一个安全的指定结果类型,所谓的安全是指避免直接调用返回类型的null值
 * 而造成空指针异常,optional.ifPresent()可以判断返回值是否为空
 * optional.get()获取返回值
 */
public class OptionalTest {
  public static void main(String[] args) {
    List<String> list = new ArrayList<String>() {{
      add("user1");
      add("user2");
    }};
    Optional<String> optional = Optional.of("user3");
    // 1.若optional不为空,list.add();
    optional.ifPresent(list::add);
    list.forEach(System.out::println);

    // 2.Optional可以在没有值时指定一个返回值
    Integer[] arr = new Integer[]{1, 3, 5, 6};

    Integer integer = Stream.of(arr).filter(x -> x > 7).max(Comparator.naturalOrder()).orElse(-1);
    System.out.println(integer);

    Integer integer1 = Stream.of(arr).filter(x -> x > 7).max(Comparator.naturalOrder()).orElseGet(() -> -1);
    System.out.println(integer1);

    // 抛出异常
    Integer integer2 = Stream.of(arr).filter(x -> x > 7).max(Comparator.naturalOrder()).orElseThrow(RuntimeException::new);
    System.out.println(integer2);
  }
}

原始类型流

在数据量比较大的情况下,将基本数据类型(int,double…)包装成相应对象流的做法是低效的,因此,我们也可以直接将数据初始化为原始类型流,在原始类型流上的操作与对象流类似,我们只需要记住两点:

  1. 原始类型流的初始化
  2. 原始类型流与流对象的转换
/**
 * 原始类型流
 * 原始类型流的初始化
 * 原始类型流与对象的转换
 */
public class OriginalStream {
  public static void main(String[] args) {
    // 1.原始类型流的初始化
    IntStream intStream = IntStream.of(1, 3);
    intStream.forEach(System.out::println);
    // 包含右边界
    IntStream intStream1 = IntStream.rangeClosed(0, 1);
    intStream1.forEach(System.out::println);
    // 不包含有边界
    IntStream intStream2 = IntStream.range(0, 1);
    intStream2.forEach(System.out::println);

    // 2.流与原始类型流的转换
    IntStream intStream4 = IntStream.of(1, 2, 3);
    // int -> Integer 装箱 boxed
    // List<Integer> collect = intStream4.boxed().collect(Collectors.toList());
    Stream<Integer> stream = intStream4.boxed();

    // Integer -> int 拆箱
    IntStream intStream3 = stream.mapToInt(Integer::new);
    intStream3.forEach(System.out::println);
  }
}

并行流

/**
 * 并行流
 * 将普通流转换为并行流,只需要调用顺序流的 parallel()方法即可
 * 调用peek方法可以看 串行流和并行流的执行顺序
 * peek:追踪流内的数据
 */
public class ParallelStream {
  public static void main(String[] args) {
    // 串行流
    Stream<Integer> peekStream = Stream.iterate(1, x -> x + 1).limit(10);
    peekStream.peek(ParallelStream::peek1).filter(x -> x > 5)
      .peek(ParallelStream::peek2).filter(x -> x < 8)
      .peek(ParallelStream::peek3)
      .forEach(System.out::println);

    // 并行流
    Stream<Integer> parallelStream = Stream.iterate(1, x -> x + 1).limit(10).parallel();
    parallelStream.peek(ParallelStream::peek1).filter(x -> x > 5)
      .peek(ParallelStream::peek2).filter(x -> x < 8)
      .peek(ParallelStream::peek3)
      .forEach(System.out::println);
  }

  // 定义方法
  public static void peek1(int x) {
    System.out.println(Thread.currentThread().getName() + ":->peek1->" + x);
  }

  public static void peek2(int x) {
    System.out.println(Thread.currentThread().getName() + ":->peek2->" + x);
  }

  public static void peek3(int x) {
    System.out.println(Thread.currentThread().getName() + ":->final result->" + x);
  }
}

串行流 peekStream 执行结果

main:->peek1->1
main:->peek1->2
main:->peek1->3
main:->peek1->4
main:->peek1->5
main:->peek1->6
main:->peek2->6
main:->final result->6
6
main:->peek1->7
main:->peek2->7
main:->final result->7
7
main:->peek1->8
main:->peek2->8
main:->peek1->9
main:->peek2->9
main:->peek1->10
main:->peek2->10

并行流 parallelStream执行结果

ForkJoinPool.commonPool-worker-2:->peek1->3
ForkJoinPool.commonPool-worker-1:->peek1->2
ForkJoinPool.commonPool-worker-2:->peek1->5
ForkJoinPool.commonPool-worker-3:->peek1->9
ForkJoinPool.commonPool-worker-2:->peek1->4
main:->peek1->7
ForkJoinPool.commonPool-worker-1:->peek1->1
ForkJoinPool.commonPool-worker-2:->peek1->8
ForkJoinPool.commonPool-worker-3:->peek2->9
ForkJoinPool.commonPool-worker-2:->peek2->8
ForkJoinPool.commonPool-worker-1:->peek1->10
ForkJoinPool.commonPool-worker-3:->peek1->6
ForkJoinPool.commonPool-worker-1:->peek2->10
ForkJoinPool.commonPool-worker-3:->peek2->6
ForkJoinPool.commonPool-worker-3:->final result->6
6
main:->peek2->7
main:->final result->7
7

总结:

我们将stream.filter(x -> x > 5).filter(x -> x < 8).forEach(System.out::println)的过程想象成管道,我们在管道上加入的peek相当于一个阀门,透过这个阀门查看流经的数据

(1)当我们使用顺序流时,数据按照源数据的顺序依次通过管道,当一个数据被filter过滤,或者经过整个管道而输出后,第二个数据才会开始重复这一过程

(2)当我们使用并行流时,系统除了主线程外启动了3个线程(和电脑配置有关)来执行处理任务,因此执行是无序的,但同一个线程内处理的数据是按顺序进行的。


sorted( )、distinct( )等对并行流的影响

对于并行流执行sorted()、distinct(),会使得运行时间大大增加,这个说法是错误的,测试表明不管是filter()还是distinct().sorted(),并行流都比串行流高效。

/**
 * 测试distinct() 对 串行流 和 并行流 执行效率的影响
 */
public class TestParallelDistinct {
  public static void main(String[] args) {
    // 生成一亿条 0~100之间的数据
    Random random = new Random();
    List<Integer> list = Stream.generate(() -> random.nextInt(100)).limit(100000000).collect(Collectors.toList());

    long begin1 = System.currentTimeMillis();
    list.stream().filter(x -> x > 10).filter(x -> x < 80).count();
    long end1 = System.currentTimeMillis();
    System.out.println("串行流执行时间:" + (end1 - begin1));
    long begin2 = System.currentTimeMillis();
    list.stream().parallel().filter(x -> x > 10).filter(x -> x < 80).count();
    long end2 = System.currentTimeMillis();
    System.out.println("并行流执行时间:" + (end2 - begin2));

    long beginDis = System.currentTimeMillis();
    list.stream().filter(x -> (x > 10)).filter(x -> x < 80).distinct().sorted().count();
    long end1Dis = System.currentTimeMillis();
    System.out.println("串行流执行排序时间:" + (end1Dis - beginDis));
    list.stream().parallel().filter(x -> (x > 10)).filter(x -> x < 80).distinct().sorted().count();
    long end2Dis = System.currentTimeMillis();
    System.out.println("并行流执行排序时间:" + (end2Dis - end1Dis));
  }
}

执行结果

串行流执行时间:943
并行流执行时间:656
串行流执行排序时间:1957
并行流执行排序时间:1125

并行流比串行流高效


总结

lambda表达式结合stream API对集合的处理非常方便,在平常项目中可以非常的省时间,提高写代码的效率。

Stream

在这里插入图片描述

Collect

在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值