JDK8新特性之Stream API(三)

JDK8新特性之Stream API(三)

JAVA8中有两个最为重要的改变第一个是Lambda表达式,这个在JDK8新特性之Lambda表达式(一),JDK8新特性之方法引用(二)这两篇文章中介绍过,另一个则是Stream API。

Stream API 介绍

Stream API(java.util.stream)是Java8中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射等操作,简而言之,Stream API提供了一种高效且易于使用的处理数据的方式。

Stream和Collection集合的区别

Collection是一种静态的内存数据结构简单说就是一个存放数据的容器,而Stream是有关计算的。前者主要是面向内存的,存储在内存中,后者主要是面向CPU,通过CPU实现计算。

Stream操作的三个步骤

Steam操作流程

  1. 创建Stream:一个数据源(如集合、数组),获取一个流
  2. 中间操作:一个中间操作链,对数据源的数据进行处理
  3. 终止操作:一旦执行终止操作,就执行中间操作链,并产生结果,之后不再被使用(意思就是之前所创建的stream已经无法使用了,要想继续操作必须重新执行三个步骤)

Stream 特点

  1. Stream自己不会存储元素(它不是一个容器)
  2. 不会改变源对象,相反他们会返回一个持有结果的新Stream(比如上图的操作1完成后会返回一个持有结果的新的stream)
  3. Stream操作是延迟执行的,这意味着它们会等到需要结果的时候才执行(只有在做终止操作的时候才会执行中间操作,换句话说如果不执行终止操作的话中间一些列操作是不会执行的)

Stream Api的使用

上面说了这么多都是概念类的东西,下面我们通过代码来解释上面的概念

步骤一:创建Stream

首先根据上面所说的Stream操作的三个步骤,第一个步骤自然就是创建Stream了,创建Stream主要有三种方式,通过集合创建、通过数组创建、通过Stream的of创建,由于这里仅仅只是创建Stream所以大家看不到效果,具体效果要等到终止操作那个步骤才能体现。
通过集合创建Stream

 @Test
    public void test01(){
        List list = new ArrayList<>();
        list.add("张三");
        list.add("李四");
        list.add("王五");
        // 返回一个顺序流,顺序流就会安装集合的顺序进行操作
        Stream stream = list.stream();
        // 返回一个并行流,并行流的意思就好比是多个同时操作,并不会保证顺序
        Stream stringStream = list.parallelStream();
    }

通过数组创建Stream

 @Test
    public void test02(){
        Integer[] arr = new Integer[]{3,5,23,55,12};
        Stream stream = Arrays.stream(arr);
    }

通过Stream的of

 @Test
    public void test03(){
        Stream stream = Stream.of(34,12,23,2,4);
    }
Stream的中间操作

下面介绍stream中间操作的一些方法,首先我们创建一些数据集来方便我们完成中间操作,先创建一个User类,并重写toString、hashCode、equals方法

public class User {
    private String username;
    private int age;

    public User(String username, int age) {
        this.username = username;
        this.age = age;
    }

    public String getUsername() {
        return username;
    }

    public int getAge() {
        return age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return age == user.age &&
                Objects.equals(username, user.username);
    }

    @Override
    public int hashCode() {
        return Objects.hash(username, age);
    }

    @Override
    public String toString() {
        return "User{" +
                "username='" + username + '\'' +
                ", age=" + age +
                '}';
    }
}

存放user数据的list

public class UserInfos {
    public static List userList = new ArrayList<>();

    static {
        userList.add(new User("詹姆斯",36));
        userList.add(new User("韦德",28));
        userList.add(new User("科比",24));
        userList.add(new User("安东尼",34));
        userList.add(new User("保罗",31));
    }

}
筛选与切片

filter(Predicate p):接收Lambda,从流中排除某些元素
distinct():筛选,通过流所生成元素的hashCode()和equals()去除重复元素
limit(long maxSize):截断流,使其元素不超过给定数量
skip(long n):跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流。与limit(n)互补

因为要执行终止操作才会执行中间操作链,所以下面例子加上了终止操作.foreach(),这个是遍历操作,关于终止操作后面会介绍
filter(Predicate p):接收Lambda,从流中过滤某些元素

    /**
     * 过滤掉年龄小于30岁的人
     */
    @Test
    public void test1() {
        List userList = UserInfos.userList;
        System.out.println(Arrays.toString(userList.toArray()));
        System.out.println("=========================================");
        // 1.获取stream流
        Stream stream = userList.stream();
        // 2.中间操作,每次中间操作都返回一个新的stream
        Stream userStream = stream.filter(user -> user.getAge() > 30);
        // 3.终止操作
        userStream.forEach(user -> System.out.print(user+","));
    }

结果:

[User{username='詹姆斯', age=36}, User{username='韦德', age=28}, User{username='科比', age=24}, User{username='安东尼', age=34}, User{username='保罗', age=31}, User{username='韦德', age=28}]
=========================================
User{username='詹姆斯', age=36},User{username='安东尼', age=34},User{username='保罗', age=31},

limit(n):截断流,使其元素不超过给定数量

/**
     * 只获取前2个元素
     */
    @Test
    public void test2()
    {
        List userList = UserInfos.userList;
        System.out.println(Arrays.toString(userList.toArray()));
        // 1.获取stream流
        Stream stream = userList.stream();
        // 2.中间操作,每次中间操作都返回一个新的stream:limit,限制元素个数
        Stream userStream = stream.limit(2);
        // 3.终止操作
        userStream.forEach(user -> System.out.print(user+","));
    }

结果:

[User{username='詹姆斯', age=36}, User{username='韦德', age=28}, User{username='科比', age=24}, User{username='安东尼', age=34}, User{username='保罗', age=31}, User{username='韦德', age=28}]
=========================================
User{username='詹姆斯', age=36},User{username='韦德', age=28},

skip(n):跳过元素,这个和limit(n)正好相反,返回一个扔掉了前n个元素的流,若流中元素不足n个,则返回一个空流

 /**
     * 去掉前4个元素
     */
    @Test
    public void test3()
    {
        List userList = UserInfos.userList;
        System.out.println(Arrays.toString(userList.toArray()));
        System.out.println("=========================================");
        // 1.获取stream流
        Stream stream = userList.stream();
        // 2.中间操作,每次中间操作都返回一个新的stream:skip,跳过指定元素个数
        Stream userStream = stream.skip(4);
        // 3.终止操作
        userStream.forEach(user -> System.out.print(user+","));
    }

结果:

[User{username='詹姆斯', age=36}, User{username='韦德', age=28}, User{username='科比', age=24}, User{username='安东尼', age=34}, User{username='保罗', age=31}, User{username='韦德', age=28}]
=========================================
User{username='保罗', age=31},User{username='韦德', age=28},

distinct():筛选,通过流所生成元素的hashCode()和equals()去除重复元素

    /**
     * 去掉重复元素
     */
    @Test
    public void test4()
    {
        List userList = UserInfos.userList;
        System.out.println(Arrays.toString(userList.toArray()));
        System.out.println("=========================================");
        // 1.获取stream流
        Stream stream = userList.stream();
        // 2.中间操作,每次中间操作都返回一个新的stream:distinct(),去掉重复元素
        Stream userStream = stream.distinct();
        // 3.终止操作
        userStream.forEach(user -> System.out.print(user+","));
    }

结果:

[User{username='詹姆斯', age=36}, User{username='韦德', age=28}, User{username='科比', age=24}, User{username='安东尼', age=34}, User{username='保罗', age=31}, User{username='韦德', age=28}]
=========================================
User{username='詹姆斯', age=36},User{username='韦德', age=28},User{username='科比', age=24},User{username='安东尼', age=34},User{username='保罗', age=31},
Stream中间操作——映射

关于映射操作这里只讲两个常用的方法,map(Function f)和flatMap(Function f),同样为了看到效果我们会在下面代码展示的时候加上终止操作.foreach()

map(Function f):接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素

/**
     * 将集合中的字符转成大写字符
     */
    @Test
    public void test5(){
        String[] arr = {"wade","james","kobe","paul","anthony"};
        System.out.println(Arrays.toString(arr));
        List strList = Arrays.asList(arr);
        //1.获取stream
        Stream stream = strList.stream();
        //2.中间操作,map映射:将元素转成大写
        Stream stringStream = stream.map(str -> str.toUpperCase());
        // 终止操作
        stringStream.forEach(str -> System.out.print(str+" "));
    }

结果:

[wade, james, kobe, paul, anthony]
WADE JAMES KOBE PAUL ANTHONY 

在上面的基础上我们再多加一个过滤的中间操作

 /**
     * 将集合中的字符转成大写字符
     */
    @Test
    public void test6(){
        String[] arr = {"wade","james","kobe","paul","anthony"};
        System.out.println(Arrays.toString(arr));
        List strList = Arrays.asList(arr);
        //1.获取stream
        Stream stream = strList.stream();
        //2.中间操作,map映射:将元素转成大写
        Stream stringStream = stream.map(str -> str.toUpperCase());
        // 中间操作,filter过滤,去掉名字是paul的元素
        Stream newStream = stringStream.filter(str -> !str.equalsIgnoreCase("paul"));
        // 终止操作
        newStream.forEach(str -> System.out.print(str+" "));
    }

简化版本

/**
     * 将集合中的字符转成大写字符
     */
    @Test
    public void test7(){
        String[] arr = {"wade","james","kobe","paul","anthony"};
        System.out.println(Arrays.toString(arr));
        List strList = Arrays.asList(arr);
       strList.stream()
               .map(str -> str.toUpperCase())
               .filter(str -> !str.equalsIgnoreCase("paul"))
               .forEach(str -> System.out.print(str+" "));
    }

结果:

[wade, james, kobe, paul, anthony]
WADE JAMES KOBE ANTHONY 

flatMap(Function f):接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
关于flatMap其实还是挺不好理解的,这里其实讲的也不好,大家就看一下大概的区别吧,具体区别是什么可以去搜一搜,因为我也不是特别懂

/**
     * 将字符串list中的所有字符串转成一个个字符,比如:
     * ["hello","world"] -> ['h','e','l','l','o','w','o','r','l','d']
     */
    @Test
    public void test8() {
        // 创建一个字符串数组
        String[] strArr = new String[]{"curry", "durant", "allen", "bosh"};
        List strList = new ArrayList<>(Arrays.asList(strArr));
        System.out.println("=================使用中间操作:map()完成=============");
        // 1. 获取stream
        Stream stream1 = strList.stream();
        // 2. 中间操作map
        Stream> streamStream = stream1.map(str -> toCharStream(str));
        // 3. 由于上一步操作完返回的是Stream>并不是Stream,因此我们遍历的时候需要两次foreach,嵌套
        streamStream.forEach(characterStream -> {
            characterStream.forEach(character -> System.out.println(character));
        });
        System.out.println("=================使用中间操作:flatMap()完成=============");
        // 1.获取stream
        Stream stream2 = strList.stream();
        // 2. 中间操作flatMap,注意这里和上面map操作时返回的stream是不一样的,这也是flatMap的作用
        Stream characterStream = stream2.flatMap(str -> toCharStream(str));
        // 返回的是 Stream可以直接进行遍历
        characterStream.forEach(character -> System.out.println(character));
    }

    /**
     * 将字符串中的多个字符构成的集合转成对应的stream实例
     *
     * @param str
     * @return
     */
    public Stream toCharStream(String str) {
        List characterList = new ArrayList<>();
        char[] chars = str.toCharArray();
        for (char c : chars) {
            characterList.add(c);
        }

        return characterList.stream();
    }

结果:

=================使用中间操作:map()完成=============
c
u
r
r
y
d
u
r
a
n
t
a
l
l
e
n
b
o
s
h
=================使用中间操作:flatMap()完成=============
c
u
r
r
y
d
u
r
a
n
t
a
l
l
e
n
b
o
s
h

Stream中间操作——排序

关于排序这里也只有两个方法分别是:
sorted():产生一个新流,按自然方式排序
sorted(comparator com) :产生一个新流,按照比较器方式排序
同样为了看到效果我们会在下面代码展示的时候加上终止操作.foreach()
sorted():产生一个新流,按自然方式排序

 @Test
    public void test9()
    {
        List list = new ArrayList<>(Arrays.asList(3,12,7,9,22,6,1));
        // 简写方式
        list.stream().sorted().forEach(num -> System.out.print(num+" "));
    }

结果:

1 3 6 7 9 12 22 

sorted(comparator com) :产生一个新流,按照比较器方式排序

 @Test
    public void test10() {
        // User和UserInfos在最开始的时候新建的数据
        List userList = UserInfos.userList;
        System.out.println(Arrays.toString(userList.toArray()));
        System.out.println("=========================================");
        // 通过比较器的方式根据年龄给user排序
        userList.stream()
                .sorted((user1, user2) -> {
                    if (user1.getAge() > user2.getAge()) {
                        return 1;
                    } else if (user1.getAge() < user2.getAge()) {
                        return -1;
                    } else {
                        return 0;
                    }
                }).forEach(num -> System.out.print(num + " "));
    }

结果:

1 3 6 7 9 12 22 
Stream的终止操作

关于终止操作,只有执行了终止操作才会进行进行一系列的中间操作也就是说其实中间操作是延迟的,其次就是在终止操作完成后stream流将不能再次使用。其实在上面的stream中间操作时为了看到效果我们都用了终止操作,只不过我们用的都是forEach(),下面将介绍Stream的一些终止操作。

Stream的终止操作——匹配与查找

allMatch(Predicate p):检查是否匹配所有元素
anyMatch(Predicate p):检查是否匹配至少一个元素
noneMatch(Predicate p):检查是否没有匹配所有元素
findFirst():返回第一个元素
findAny():返回当前元素的任意元素
count():返回流中元素的总个数
max(Comparator c):返回流中最大值
min(Comparator c):返回流中最小值
forEach(consumer c):内部迭代

前面用了很多次的forEach,我们先来看看它的用法,以及说明一下在终止操作完成后stream流将不能再次使用是啥意思

/**
     * forEach(consumer c):内部迭代
     */
    @Test
    public void test11() {
        List strList = new ArrayList<>(Arrays.asList("curry", "durant", "allen", "bosh"));
        System.out.println("遍历集合所有元素:");
        Stream stream = strList.stream();
        stream.forEach(str -> System.out.println(str));
        // 由于forEach是终止操作,这时候stream已经不能再次使用了,下面这个代码执行是会报错:java.lang.IllegalStateException: stream has already been operated upon or closed
//        stream.forEach(str -> System.out.println(str));
    }

结果:

遍历集合所有元素:
curry
durant
allen
bosh
    /**
     * allMatch(Predicate p):检查是否匹配所有元素
     * anyMatch(Predicate p):检查是否匹配至少一个元素
     * noneMatch(Predicate p):检查是否没有匹配所有元素
     * findFirst():返回第一个元素
     * findAny():返回当前元素的任意元素
     * count():返回流中元素的总个数
     * max(Comparator c):返回流中最大值
     * min(Comparator c):返回流中最小值
     * forEach(consumer c):内部迭代
     */
    @Test
    public void test11() {
        List strList = new ArrayList<>(Arrays.asList("curry", "durant", "allen", "bosh"));
        boolean flag1 = strList.stream().allMatch(str -> str.contains("a"));
        System.out.println("所有元素是否都含有字母a:" + flag1);

        boolean flag2 = strList.stream().anyMatch(str -> str.contains("a"));
        System.out.println("是否至少一个元素含有字母a:" + flag2);

        boolean flag3 = strList.stream().noneMatch(str -> str.contains("hh"));
        System.out.println("是否所有元素都不包含hh的元素:" + flag3);

        // 这里Optional不做介绍,大家可以自行百度
        Optional first = strList.stream().findFirst();
        String element = first.get();
        System.out.println("集合中的第一个元素是:" + element);

        // 这里用parallelStream能体现效果
        Optional any = strList.parallelStream().findAny();
        String el = any.get();
        System.out.println("集合中的任意一个元素是:" + el);

        long count = strList.stream().count();
        System.out.println("集合中的总个数是:" + count);


        List intList = new ArrayList<>(Arrays.asList(3, 12, 7, 9, 22, 6, 1));
        // 1.匿名函数方式
        Optional max = intList.stream().max(new Comparator() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return Integer.compare(o1, o2);
            }
        });
        // 2.lambda表达式方式
//        intList.stream().max((num1,num2)->{
//            return Integer.compare(num1,num2);
//        });
        System.out.println("集合中的最大值是:" + max.get());

        // 1.匿名函数方式
        Optional min = intList.stream().min(new Comparator() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return Integer.compare(o1, o2);
            }
        });
        // 2.lambda表达式方式
//        intList.stream().max((num1,num2)->{
//            return Integer.compare(num1,num2);
//        });
        System.out.println("集合中的最小值是:" + min.get());
    }

结果:

所有元素是否都含有字母a:false
是否至少一个元素含有字母a:true
是否所有元素都不包含hh的元素:true
集合中的第一个元素是:curry
集合中的任意一个元素是:allen
集合中的总个数是:4
集合中的最大值是:22
集合中的最小值是:1
Stream的终止操作——规约

Optional reduce(BinaryOperator accumulator):可以将流中元素反复结合起来,得到一个值。返回Optional

@Test
    public void test12(){
        List list = Arrays.asList(1,2,3,4,5);
        /*
            给定初始值和集合中的元素进行求和:
            reduce操作其实内部进行的是:10+num1,(10+num1)+num2,(10+num1+num2)+num3....
         */
        Integer result = list.stream().reduce(10, (num1, num2) -> {
            return Integer.sum(num1, num2);
        });
        System.out.println(result);
    }

结果:25

T reduce(T identity, BinaryOperator accumulator):可以将流中元素反复结合起来,得到一个值。返回T

@Test
    public void test13(){
        List list = Arrays.asList(1,2,3,4,5);
        /*
            给定初始值和集合中的元素进行求和,和上一个例子不同的是它不需要传递一个初始值,而且返回的是个Optional
         */
        Optional result = list.stream().reduce((num1, num2) -> {
            return Integer.sum(num1, num2);
        });
        System.out.println(result.get());
    

结果:15

Stream的终止操作——收集

collect(Collector c):将流转换成为其他形式。接收一个Collector接口的实现,用于给Stream中元素做汇总方法。
Collector接口中方法的实现决定了如何对流执行收集的操作(如收集到List、Set、Map),另外Collectors实用类提供了很多静态方法,可以方便地创建常见收集器时期,我们下面以常用的Collectors.toList和Collectors.toSet举例,其他的可以自行看API

 @Test
    public void test14() {
        List list = Arrays.asList("a", "ac", "bc", "de", "ea", "a");
        System.out.println("原集合:" + list);
        //1.获取一个只包含字母a的字符串的List集合
        List aLIst = list.stream().filter(str -> str.contains("a")).collect(Collectors.toList());
        System.out.println("只包含字母a字符串的新List集合:" + aLIst);

        //2.获取一个值包含字母a的字符串的Set集合
        Set aSet = list.stream().filter(str -> str.contains("a")).collect(Collectors.toSet());
        System.out.println("只包含字母a字符串的新Set集合:" + aSet);


    }

结果:

原集合:[a, ac, bc, de, ea, a]
只包含字母a字符串的新List集合:[a, ac, ea, a]
只包含字母a字符串的新Set集合:[a, ac, ea]

总结

在使用Stream API的时候要注意先要了解Lambda表达式,如果对Lambda表达式还看的不是太懂了话可能对于Stream API的使用有一定困难,Stream API只是一个API,刚开始看的时候可能很不习惯甚至别扭,只要多练、多用、多看,看得多了练得多了用起来自然也就熟练了。

你可能感兴趣的:(JDK8新特性之Stream API(三))