Java8-Stream详解

Java8-Stream

1. Stream

1.1 Stream介绍

1.1.1 介绍

  • Java 8 API添加了一个新的抽象称为流 Stream,可以让你以一种声明的方式处理数据。

    Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。

  • Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。

    这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。

  • 元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果

1460000018919149

1.1.2 Stream特点

(1)数据源

  • 数据源可以是集合,数组,I/O channel,产生器generator等(Stream本身并不保存数据

(2)支持聚合操作

  • 聚合操作:类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等

(3)Pipelining

  • 中间操作都会返回流对象stream本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)

(4)内部迭代

  • 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现

(5)延迟执行

  • 中间的操作只是提供了一个操作逻辑,并记录,只有最终求值时才会执行传入的操作

1.1.3 直接对集合遍历操作与使用Stream操作集合的对比

(1)使用循环遍历

  • 如果是处理简单数据,进行简单操作,使用循环遍历集合,并没有什么不妥,但是如果是复杂的大型的数据使用循环不仅会使处理效率变得底下,而且会降低代码的可读性

    • 例如下面代码:查找符合条件的学生

      • 以下是通过遍历集合,通过条件判断来对符合条件的数据进行操作
      class Student implements Comparable<Student> {
          int id;
          String name;
          String className;
          int grade;
      
          public Student(int id, String name, String className, int grade) {...}
      
          @Override
          public String toString() {...}
      
          //使用成绩决定排名
          @Override
          public int compareTo(Student o) {
              return this.grade-o.grade;
          }
      }
      
      public class StreamTest {
          public static void main(String[] args) {
              LinkedList<Student> studentList = new LinkedList<Student>();
              studentList.add(new Student(1, "Alice", "1班", 100));
              studentList.add(new Student(2, "Bob", "2班", 85));
              studentList.add(new Student(3, "Coco", "2班", 46));
              studentList.add(new Student(4, "Dav", "3班", 95));
              studentList.add(new Student(5, "Elizabeth", "4班", 81));
              studentList.add(new Student(6, "Fermat", "4班", 69));
              studentList.add(new Student(7, "grace", "4班", 59));
              //选取4班,成绩大于60的人,和其他班成绩大于50的人
              //由于是链表,为了性能,这里使用iterator进行迭代
              Iterator<Student> iter = studentList.listIterator();
              while(iter.hasNext()){
                  Student cur = iter.next();
                  if("4班".equals(cur.className)){
                      if(cur.grade>60) {
                          System.out.println(cur);
                      }
                  }
                  else if(cur.grade>50){
                      System.out.println(cur);
                  }
              }
          }
      }
      
      • 这只是一个很简单的例子,如果说数据量很大或者涉及几种数据,而且条件设置地比较复杂,代码量以及执行效率会变得非常差

(2)使用Stream操作

  • 以下是通过Stream处理集合实例

    studentList.stream().filter(s->{
        if("4班".equals(s.className)){
            return s.grade > 60;
        }else {
            return s.grade > 50;
        }
    }).forEach(s-> System.out.println(s));
    

1.2 Stream使用

1.2.1 使用流程

(1)流程

  • 当使用一个流时,通常包括三个基本步骤

    获取数据源
    数据转换
    传入需要执行的操作
    操作集合获取想要的结果
  • 当一个Stream流执行完终结方法时,就不能再使用该对象了,再次使用会抛出(IllegalStateException

1.2.2 获取Stream接口对象

(1)Stream接口

  • Stream接口是定义在java.util.stream.Stream<T>

  • public interface Stream<T> extends BaseStream<T, Stream<T>> {
        ...
    }
    

(2)Stream接口实现对象的获取方式

  • Collection实现的stream()parallelStream()接口方法,创建一个顺序流:

    • java8中将Collection接口中加入了stream()parallelStream()默认方法,用于获取Stream对象

      public interface Collection<E> extends Iterable<E> {
          ...
          default Stream<E> stream() {
                  return StreamSupport.stream(spliterator(), false);
      	}
          
          ...
          default Stream<E> parallelStream() {
              return StreamSupport.stream(spliterator(), true);
          }
          ...
      }
      
      List<String> list = new ArrayList<>();
      Stream<String> stream = list.stream(); //获取一个顺序流
      Stream<String> parallelStream = list.parallelStream(); //获取一个并行流
      
      • 对于Map类型集合可以先获取keyset,再获取键的Stream对象;或者先获取values(),再获取值的Stream对象;也可以获取entrySet(),再获取键值对Stream对象
  • Arrays.stream(T[] array)静态方法:

    • public static <T> Stream<T> stream(T[] array) {
      	return stream(array, 0, array.length);
      }
      
      public static <T> Stream<T> stream(T[] array, int startInclusive, int endExclusive{
      	return StreamSupport.stream(spliterator(array,startInclusive,endExclusive), false);
      }
      
      String[] nums = new String[10];
      Stream<String> stream = Arrays.stream(nums);
      
  • Stream中的静态方法:

    • public static<T> Stream<T> of(T... values) {
      	return Arrays.stream(values);
      }
      
      • 此方法将通过数组对象获取Stream对象
    • public static<T> Stream<T> generate(Supplier<T> s) {
          Objects.requireNonNull(s);
          return StreamSupport.stream(
              new StreamSpliterators.InfiniteSupplyingSpliterator.OfRef<>(Long.MAX_VALUE, s), false);
      }
      
    • public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f) {...}
      
  • BufferedReader对象的lines() 方法

    • public Stream<String> lines(){...}
      
      BufferedReader reader = new BufferedReader(new FileReader("/user/sd/readme.md"));
      Stream<String> lineStream = reader.lines();
      
  • 使用 Pattern.splitAsStream() 方法,将字符串分隔成流

    • Pattern pattern = Pattern.compile(",");
      Stream<String> stringStream = pattern.splitAsStream("a,b,c,d");
      stringStream.forEach(System.out::println);
      

参考

1.2.3 延迟方法

  • 延迟方法:一般为传入某种函数式接口来表明需要进行的操作,其返回类型依旧为Stream类型,所以可以链式调用(该系列方法并没有执行操作,只是传入函数式接口表明需要进行的操作)

(1)Stream filter(Predicate <? super T> predicate)

  • 该方法通过传入predicate接口对象,作为筛选条件

    List<String> personList = new ArrayList();
    personList.add("Alice");
    personList.add("Bob");
    personList.add("Coco");
    Stream<String> stream = personList.stream()
    stream.filter(s->s.startsWith("A")).forEach(System.out::println);//Alice
    

(2)Stream map(Functionsuper <T, ? extends R> mapper)

  • 该接口需要一个Function函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流

    Stream<String> stream = Stream.of("1", "2", "3");
    Stream<Integer> result = stream.map(Integer::parseInt);
    

(3)Stream limit(long maxSize)

  • 该方法可以对流进行截取,只取用前n个

  • 参数是一个long型,如果集合当前长度大于参数则进行截取;否则不进行操作

    stream.limit(2).forEach(System.out::println);//Alice Bob
    

(4)Stream skip(long n)

  • 可以使用 skip 方法获取一个跳过n个数据后的新流

  • 如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流

    stream.skip(1).forEach(System.out::println);//Bob Coco
    

(5)Stream<T> distinct()

  • 该方法会将重复元素给去除掉

  • 该方法使用hashCode()equals(Object o)方法来比较元素,只有两者都相同才认为是相同的,只会保留第一个所遇到的元素

    public class StreamTest {
    
        public static void main(String[] args){
            List<String> list = new ArrayList();
            StreamTest s = new StreamTest();
            list.add("1");
            list.add("2");
            list.add("1");
            list.add(new String("1"));
            list.stream().distinct().forEach(System.out::println);//1 2
        }
    }
    
  • 当我们需要去重时也可以使用Set去重:

    public static void main(String[] args) {
        users.parallelStream().filter(distinctByKey(User::getId))
            .forEach(System.out::println);
    }
    
    
    public static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
        Set<Object> seen = ConcurrentHashMap.newKeySet();
        return t -> seen.add(keyExtractor.apply(t));
    }
    
    • 通过Function接口处理数据后,将处理后的数据添加尝试添加进Set,如果添加不成功就代表已经有改元素了,返回false,此时过滤器函数会将改元素丢弃,实现去重

(6)<R> Stream<R> map(Function<? super T, ? extends R> mapper)

  • 该方法通过传入Function接口实例将数据元素进行元素转换,构造新的Stream对象

  • 此外还有方法XXStream mapToXX(ToXXFunction<? super T> mapper)等一系列方法,来将数据转换为特定类型

    List<String> list = new ArrayList();
    StreamTest s = new StreamTest();
    list.add("String");
    list.add("哈哈");
    list.add(new String("hello"));
    list.stream().map(String::toUpperCase).forEach(System.out::println);//STRING 哈哈 HELLO
    

(7)Stream<T> sorted(Comparator<? super T> comparator)

  • 该方法使用传入的Comparator进行比较,然后排序,构建排序后的新Stream对象

  • 还包含一个无参的构造方法

    list.add("String");
    list.add("哈哈");
    list.add(new String("hello"));
    list.stream().sorted(String::compareTo).forEach(System.out::println);//String hello 哈哈
    

(8)Stream<T> peek(Consumer<? super T> action)

  • 该方法通过传入Consumer接口对元素进行一些操作,并返回新的Stream对象

    class A{
        int var = 0;
    
        void display(){
            System.out.println(var);
        }
    }
    
    List<A> list = new ArrayList();
    StreamTest s = new StreamTest();
    list.add(new A());
    list.add(new A());
    list.add(new A());
    list.stream().peek(o->o.var = 1).forEach(A::display);
    

(7)static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)

  • 该静态方法可以将两个流合并成一个新Stream对象

  • 该方法传入的流的泛型类型要一致

    stream = Stream.concat(stream,Stream.of("1","2"));
    stream.forEach(System.out::println);//Alice Bob Coco 1 2
    

1.2.4 终结方法

  • 终止方法:表明已经要求通过之前传入的操作进行数据处理,并给出结果

(1)void forEach(Consumersuper <T> action)

  • 该方法接收一个 Consumer 接口对象,会将每一个流元素交给该函数进行处理

    List<String> personList = new ArrayList();
    personList.add("Alice");
    personList.add("Bob");
    personList.add("Coco");
    personList.stream().forEach(s->{
        if(s.startsWith("A")){
            System.out.println(s);
        }
    });//打印以A开头的字符串
    

(2)long count()

  • 该方法返回操作后的元素个数+

    System.out.println(stream.filter(s -> s.startsWith("A")).count());//1
    

(3)Object[] toArray()

  • 该方法将数据转入到数组中,并返回

    List<String> list = new ArrayList();
    StreamTest s = new StreamTest();
    list.add("String");
    list.add("哈哈");
    list.add(new String("hello"));
    System.out.println(Arrays.toString(list.stream().sorted(String::compareTo).toArray()));//[String, hello, 哈哈]
    

发布了19 篇原创文章 · 获赞 6 · 访问量 728

猜你喜欢

转载自blog.csdn.net/qq_42631607/article/details/105363252