Java8新特性--Stream流操作

Posted Lucas小毛驴博客

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java8新特性--Stream流操作相关的知识,希望对你有一定的参考价值。

一.Stream的介绍

? Stream流操作是Java8 新增的重要特性,与之前学习的java.io包里的字节流和字符流是完全不同的概念,不是同一个东西。

? 这里的Stream流操作是java8针对集合操作的增强,专注于对集合的各自高效、便利、优雅的聚合操作

? Stream不是集合元素,也不是数据结构,并不保存数据,它是有关算法和计算的,使用起来更像一个高级的迭代器,我们只需要给出需要对其流中的元素执行什么操作,Stream就会隐式的在内部进行遍历,作出相应的数据操作。

? Stream的处理是单向的,这一点和迭代器一样,数据只能遍历一次,一次过后就用完了,下一次得重头来,每次转换原有的Stream对象不会改变,而是返回一个新的Stream对象,这样就允许了操作可以是链式的。

? Stream流的使用是按照一定的步骤进行的,大概如下流程:

? 获取数据源 --> 数据处理 --> 执行操作获取想要的结果

二.Stream的获取

java.util.stream包下有Interface Stream<T>,这个就是java8新加入的最常用的流接口。

想要获取一个流,常用有以下几种方式:

  • 所有的Collection集合都可以通过stream默认方法获取流。
  • Stream接口的静态方法of可以获取数组对应的流。
import java.util.*;
import java.util.stream.Stream;

public class StreamTest {
    public static void main(String[] args) {
        // Collection获取流
        ArrayList<String> arrayList = new ArrayList<>();
        
        Stream<String> stream = arrayList.stream();

        // Stream静态方法of获取流
        String[] arr = {"zhangsan", "lisi", "wangwu"};
        Stream<String> stream1 = Stream.of(arr);

        // Map 不是Collection的子接口,获取流要分成对应的key和value
        Map<String,String> map = new HashMap<>();
        Stream<String> keyStream = map.keySet().stream();
        Stream<String> valueStream = map.values().stream();
    }
}

三.Stream常用的方法

Stream流操作一些常用API,可以分为两类:

  • 延迟方法:返回值还是Stream接口自身类型的方法,支持链式调用。(除了终结方法,其余方法都是延迟方法)
  • 终结方法:返回值不再是Stream接口自身类型的方法,因此不再支持链式调用。

3.1 终结方法

  • forEach

    void forEach(Consumer<? super T> action)

    该方法与for循环中的for-each是不同的,该方法接收一个Consumer接口函数,会将每一个流元素交给该函数进行处理,它是一个终结操作,执行之后Stream流中的数据都会被消费掉。

    public class StreamTest {
        public static void main(String[] args) {
            Stream<String> stream = Stream.of("zhangsan", "lisi", "wangwu");
            stream.forEach(System.out::println);
        }
    }
    // 输出:zhangsan lisi wangwu
  • count

    long count():返回流中元素的个数。是一个终结方法

    public class StreamTest {
        public static void main(String[] args) {
            Stream<Integer> stream = Stream.of(11, 33, 55, 77, 99);
            long count = stream.count();
            System.out.println(count);
        }
    }
    // 输出: 5

3.2 延迟方法

  • filter

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

    之前所学**Predicate**是函数式接口,唯一的抽象方法是**test()**,该方法会返回一个boolean值,如果为true,filter方法就会留下元素,如果为false,则会丢弃元素。

    总的来说,filter就是进行数据筛选,根据条件选择想要的元素,放在一个新的Stream

    举例:挑选出偶数数字

    public class StreamTest {
        public static void main(String[] args) {
            List<Integer> array = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
            array.stream().filter((num) -> num % 2 == 0).forEach(System.out::println);
        }
    }
    // 输出:2 4 6 8 10
  • map

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

    map将流中的元素映射成另一个流中的元素,该接口需要一个Function函数式接口作为参数,可以将当前流中T类型数据转换为另一种R类型的流。

    举例:将数字映射为数字的平方

    public class StreamTest {
        public static void main(String[] args) {
            Stream<Integer> stream1 = Stream.of(1,2,3,4,5);
            stream1.map((num) -> num * num).forEach(System.out::println);
        }
    }
    // 输出:1 4 9 16 25
  • limit

    Stream<T> limit(long maxSize)

    limit方法可以对流进行截取,只取前几个

    public class StreamTest {
        public static void main(String[] args) {
            List<Integer> arr = Arrays.asList(11,22,33,44,55);
            arr.stream().limit(2).forEach(System.out::println);
        }
    }
    // 输出:11 22
  • skip

    Stream<T> skip(long n):用于跳过前几个元素,获取一个截取之后的新流。

    public class StreamTest {
        public static void main(String[] args) {
            List<Integer> arr = Arrays.asList(11,22,33,44,55);
            arr.stream().skip(2).forEach(System.out::println);
        }
    }
    // 输出:33 44 55
  • concat

    static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b):用于将两个流合并成一个流,这个是Stream的静态方法,与String类中的concat方法是不同的。

    public class StreamTest {
        public static void main(String[] args) {
            Stream<Integer> stream = Stream.of(11, 33, 55);
            Stream<Integer> stream1 = Stream.of(22,44);
            Stream.concat(stream, stream1).forEach(System.out::println);
        }
    }
    // 输出: 11 33 55 22 44

四.Stream的使用

  1. 需要特别注意处理顺序,中间操作(延迟方法)的一个重要特性是惰性,没有调用终结方法时候是不会执行的。

    处理顺序为什么很重要,以下例子可以看出:

    Stream.of("d2", "a2", "b1", "b3", "c")
        .map(s-> {
            System.out.println("map: " + s);
            return s.toUpperCase();
        })
        .filter(s-> {
            System.out.println("filter: " + s);
            return s.startsWith("A");
        })
        .forEach(s-> System.out.println("forEach: " + s));
    // 输出结果为:
    // map: d2
    // filter: D2
    // map: a2
    // filter: A2
    // forEach: A2
    // map: b1
    // filter: B1
    // map: b3
    // filter: B3
    // map: c
    // filter: C

    由结果可以看出,每个字符串都是调用了map和filter,有五个字符串,就调用了5次map和filter,而forEach只调用一次,因为经过filter过滤后满足A开头的字符串只有一个。

    但当我们改变中间操作的顺序时候,是可以减少很多操作的:

    Stream.of("d2", "a2", "b1", "b3", "c")
            .filter(s-> {
                System.out.println("filter: " + s);
                return s.startsWith("a");
            })
            .map(s-> {
                System.out.println("map: " + s);
                return s.toUpperCase();
            })
            .forEach(s-> System.out.println("forEach: " + s));
    // 输出为:
    // filter: d2
    // filter: a2
    // map: a2
    // forEach: A2
    // filter: b1
    // filter: b3
    // filter: c

    现在,map只被调用了一次,因此操作管道在大量元素输入时执行得更快。

    在编写复杂的方法链时,必须记住操作顺序很重要!

更加详细内容可以参考文章学习:Java 8 Stream 教程

以上是关于Java8新特性--Stream流操作的主要内容,如果未能解决你的问题,请参考以下文章

java8新特性Stream流操作详解及实战3

java8新特性Stream流操作详解及实战3

java8新特性Stream流操作详解及实战3

乐字节-Java8新特性-接口默认方法之Stream流(下)

Java8 新特性之集合操作Stream

java8新特性