• java8--stream


    java8-stream

    Stream API

    Stream 是java8中处理集合的关键抽象概念,他可以制定你希望对集合进行的操作,但是将执行操作的时间交给具体实现来决定。

    要点

    • 可以从集合、数组、生成器或者迭代器中创建Stream。
    • 使用filter来选择元素,使用map来改变元素。
    • 其他改变Stream的操作包括limit、distinct、sorted。
    • 要从Stream中获得结果,请使用reduction操作符,例如count、max、min、findFirst或findAny、其中一些方法会返回一个Optional值。
    • Optional类型的目的是为了安全的替代使用null值。要想安全的使用,需要借助ifPresent和orElse方法。
    • 可以收集集合、数组、字符串或map中的Stream结果。
    • Collectors类的groupingBy和partitioningBy允许对Stream中的内容分组,并获取每个组的结果。
    • java8对原始类型int、long和double提供了专门的Stream。
    • 当使用并行Stream时,请确保不带有副作用,并考虑放弃排序约束。

    Stream Example:

        @Test//*不使用Stream的方法*
        public void testCount(){
            List<String> words = getWords();
            int count = 0;
            for (String w: words) {
                if(w.length()>10)count++;
            }
            System.out.println(count);
        }
        @Test//*使用Stream*
        public void testStream(){
            List<String> words = getWords();
            long count=0;
            count =words.stream().filter(w->w.length()>10).count();
            System.out.println(count);
        }
        public List<String> getWords(){
            List<String> words = new ArrayList<String>();
            for (int i = 0; i < 20; i++) {
                words.add("aaabbbccc"+i);
            }
            return words;
        }
    


    可以看出来Stream给我们带来了很大的方便性和可读性
    当使用Stream时,通过三个阶段来建立一个操作流水线
    1. 创建一个Stream
    2. 在一个或多个步骤中,制定将初始Stream转换为另一个Stream的中间操作。
    3. 使用一个终止操作来产生一个结果。该操作会强制他之前的延迟操作立即执行。在这之后,该Stream就不会在被使用了。

    创建Stream

    java8在Collection接口中添加了stream方法,可以将任何一个集合转化为一个Stream。

        Stream<String> words = Stream.of(contents.split(","));
        //split方法返回一个String[]数组
        //of方法接受可变长度的参数,因此可以构造一个含有任何个参数的Stream
        Stream<String> words = Stream.of("hello","world","java8","lambda");
    


    使用Arrays.stream(array,from,to)方法将数组的一部分转化为Stream
    如果要创建一个不含任何元素的Stream,可以使用静态的Stream.empty方法:

    Stream<String> words = Stream.empty();
    


    Stream接口有两个用来创建无限Stream的静态方法。generate方法接受一个无参函数,

        @Test
        public void testInit(){
            Stream<String> echos = Stream.generate(()->"Echo");
            Stream<Double> randoms= Stream.generate(Math::random);
        }
    


    Java8中添加了许多能够产生Stream的方法。比如 Pattern类添加了一个splitAsStream方法。

    Stream<String> words = Pattern.complie(",").splitAsStream(contents);
    

    filter、map和flatMap方法

    • filter方法的参数是一个Predicate对象–即一个从T到boolean的函数。
      Stream<String> words = getWords().stream().filter(w->w.length()>10);
      
    • 当对一个流中的值进行某种转换时,使用map方法。
      //将单词转换为大写
      Stream<String> words = getWords().stream().map(String::toUpperCase);
      //获得单词的第一个字母
      Stream<Character> wordsFirstChar = getWords().stream().map(s->s.charAt(0));
      
    • flatMap
          @Test
          public void testMapAndFlatMap(){
              Stream<Stream<Character>> chars = getWords().stream().map(w->getCharacter(w));
              //这里可以用flatMap
              //flatMap 会对每个对象调用getCharacter()方法,并展开结果
              Stream<Character> charss=getWords().stream().flatMap(w->getCharacter(w));
          }
          public Stream<Character> getCharacter(String s){
              List<Character> chars = new ArrayList<Character>();
              for (char c: s.toCharArray()) {
                  chars.add(c);
              }
              return chars.stream();
          }
      

    limit()、skip()和concat():提取子流和组合流

    • Stream.limit(n)会返回一个包含n个元素的新流如果原始流长度小于n,则返回原始流
      //返回1个具有100个随机数的流
      Stream<Double> randoms100 = Stream.generate(Math::random).limit(100);
      
    • Stream.skip(n)会跳过n个元素。
      //跳过第一个单词组成流
      Stream<String> wordsWithoutFirst = getWords().stream().skip(1);
      System.out.println(wordsWithoutFirst.count());//打印为 19  略过了第一个元素
      
    • Stream.concat() 静态方法。合并两个流。
      合并流的第一个应该为无限状态
          Stream<String> total = Stream.concat(getWords().stream().skip(10), getWords().stream());
          System.out.println(total.count());
      

    distinct()、Sorted():有状态转换

    上面的流转换都是无状态的。也就是获得的结果不依赖前面的元素。java8也提供了有状态的转换。例如distinct()
    distinct()方法需要记录已存储的单词。然后判断重复及是否添加

        Stream<String> unique = Arrays.asList("todo","todo","todo","today").stream().distinct();
        System.out.println(unique.count());//2
    


    sorted方法排序必须遍历整个流。并在产生任何元素之前对他进行排序。

        Stream<String> sortWords = getWords().stream().sorted(Comparator.comparing(String::length).reversed());
    

    Collections.sort方法会对原有集合进行排序,Stream.sorted方法会返回一个新的已排序的流;

    聚合方法–终止流操作

    聚合方法都是终止操作。当一个流应用了终止操作以后,他就不能在进行其他操作了。
    聚合方法主要有:count(),max(),min(),findFirst,findAny,allMatch,noneMatch等

        //找到排序最大的单词 min同理
        Optional<String> max = getWords().stream().max(String::compareToIgnoreCase);
        if(max.isPresent()){
            System.out.println(max.get());
        }
        //找到所有的以a开头的单词
        Optional<String> words = getWords().stream().filter(s->s.startsWith("a")).findAny();
        //找到所有的以a开头的第一个单词
        Optional<String> wordFirst = getWords().stream().filter(s->s.startsWith("a")).findFirst();
    


    allMatch,noneMatch分别表示所有元素和没有元素匹配predicate返回true。

    Optional类型

    • Optional对象或者是对一个T类型对象的封装,或表示不是任何对象。他比一般指向T类型的引用更安全,因为他不会返回null–但也是在正确的使用前提下。
      如果存在被封装的对象,那么 get方法会返回该对象,否则会抛出一个NoSuchElementException.所以,可以用isPresend()来判断一下。
          Optional<String> max = getWords().stream().max(String::compareToIgnoreCase);
              if(max.isPresent()){
                  //do something
              }
      
    • 高效使用Optional的关键在于,使用一个或接受正确值、或者返回另一个替代值的方法
      String result = max.orElse("");
      

      如果没有值则用“”替代。
      此外orElse还有两个方法
      orElseGet(Lambda):可以调用代码计算值
      orElseThrow(Exception):抛出异常
    • 创建optional
      利用Optional的静态方法可以创建对象,比如Optional.of(result)或者Optional.empty();
      ofNullable(obj)方法会跟据obj的值来进行创建optional,如果obj==null,那么这个方法间接等于Optional.empty(),
      如果obj!=null 那么 间接等于Optional.of(obj);
      +收集结果
      可以通过iterator方法生成一个迭代器,用来访问结果的元素,或者使用stream.toArray(),他会返回一个Object[]数组。如果想获得具体类型的。
      可以把类型传递给数组的构造函数
      String[] result = words.toArray(String[]::new);
      

      如果要把结果收集到一个HashSet中,如果这个过程是并行的,那么不能将元素放到一个单独的Hashset中,因为HashSet不是线程安全的。所以要使用collect方法,他接受三个参数
      1. 一个能创建目标类型实例的方法,例如hashset的构造函数。
      2. 一个江元素添加到目标中的方法,例如一个add方法。
      3. 一个将两个对象整合到一起的方法,例如addALl方法。
      HashSet<String> result = stream.collect(HashSet::new,HashSet::add,HashSet::addAll);
      

      在实际中,通常不需要这么做,因为Collector借口已经提供了这三个方法,
      List<String> result = getWords().stream().collect(Collectors.toList());
      List<String> result = getWords().stream().collect(Collectors.toSet());
      TreeSet<String> result = stream.collect(Collectors.toCollection(TreeSet::new));
      

      Collectors还有许多方法,可以自己去看一下api

    原始流类型

    主要有IntStream、DoubleStream、LongStream等。
    IntStream:short、char、byte、boolean
    DoubleStream:float
    LongStream:long

    IntStream intStream = IntStream.of(1,2,3,4);
    intStream = Arrays.stream(value,from,to);  //value 为一个int[]数组
    
    • 原始流和对象流转换

    当有一个对象流的时候,可以通过mapToInt、mapToLong或者mapToDouble方法将他们转换为一个原始流。

    Stream<String> words = getWords();
    IntStream length = words.mapToInt(String::length);
    


    将一个原始流转换为对象流时,可以使用boxed方法。

    Stream<Integer> integers = IntStream.range(0,100).boxed();
    

    并行流

    默认情况下,流操作会创建一个串行流,方法Collection.parallelStream()除外。parallel方法可以将任意一个串行流变为一个并行流。

    Stream<String> words = getWords().stream().parallel();
    


    只要在终止方法执行时,流处于并行模式,那么所有的延迟执行的流操作就都会被并行执行。
    当并行运行流时,他应返回与串行运行时相同的结果。这些操作都是无状态的,因此可以以任意顺序被执行
    下面是一个反例,用来提示不应该这么做,假设你需要绩一个字符串流中所有短单词的总数:

            int[] shortWords = new int[11];
            Stream<String> words = Arrays.asList("java","c++","javac","eclipse","tomcat","helloworld").stream();
            words.parallel().forEach(s->{if(s.length()<5)shortWords[s.length()]++;});
            System.out.println(Arrays.toString(shortWords));
    


    传递给foreach的函数会在多个线程中并发运行,来更新一个共享数组,这是一个经典的条件竞争。如果多运行几次,很有可能会得到不同的总数,并且没有一个是对的。
    使用并行流操作的函数都应该是线程安全的。

    当执行一个流(串行和并行)操作时,并不会修改流底层的集合,流不会收集他自己的数据–这些数据总是存在另一个集合中。如果想要修改原有的集合,那么就无法定义流操作的输出。

    下面这么做是可以的。

    List<String> words = ...;
    Stream<String> wordStream=words.stream();
    words.add("end");
    long n  = wordStream.distinct().count();
    


    下面是不可以的,因为干扰了原集合

    Stream<String> wordStream = words.stream();
    wordStream.forEach(s->if(s.length<12)words.remove(s));
    
  • 相关阅读:
    从头到尾彻底理解KMP
    [CF1220E] Tourism
    [CF446C] DZY Loves Fibonacci Numbers
    [CF1003E] Tree Constructing
    [CF1238E] Keyboard Purchase
    [CF915E] Physical Education Lessons
    [CF788B] Weird journey
    [CF1371E2] Asterism (Hard Version)
    [CF780E] Underground Lab
    [CF372C] Watching Fireworks is Fun
  • 原文地址:https://www.cnblogs.com/-10086/p/4617098.html
Copyright © 2020-2023  润新知