【JDK8 新特性 5】Stream流介绍和常用方法的使用

简介: 当我们需要对集合中的元素进行操作的时候,除了必需的添加、删除、获取外,最典型的就是集合遍历。我们来体验集合操作数据的弊端。

1、 Stream 流介绍

当我们需要对集合中的元素进行操作的时候,除了必需的添加、删除、获取外,最典型的就是集合遍历。我们来体验集合操作数据的弊端。

一个ArrayList集合中存储有以下数据:张三,李四,王五,张小明
需求:
1.拿到所有姓张的
2.拿到名字长度为3个字的
3.打印这些数据

代码如下:

public class Test {

    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list, "张三", "李四", "王五", "张小明", "张二狗");
        // 1.拿到所有姓张的
        ArrayList<String> zhangList = new ArrayList<>();

        for (String name : list) {
            if (name.startsWith("张")) {
                zhangList.add(name);
            }
        }
        // 2.拿到名字长度为3个字的
        ArrayList<String> threeList = new ArrayList<>();
        for (String name : zhangList) {
            if (name.length() == 3) {
                threeList.add(name);
            }
        }
        // 3.打印这些数据
        for (String name : threeList) {
            System.out.println(name);
        }
    }
}

循环遍历的弊端
这段代码中含有三个循环,每一个作用不同:

  1. 首先筛选所有姓张的人;
  2. 然后筛选名字有三个字的人;
  3. 最后进行对结果进行打印输出。

每当我们需要对集合中的元素进行操作的时候,总是需要进行循环、循环、再循环。
这是理所当然的么?不是。
循环是做事情的方式,而不是目的。每个需求都要循环一次,还要搞一个新集合来装数据,如果希望再次遍历,只能再使用另一个循环从头开始。
那Stream能给我们带来怎样更加优雅的写法呢?
Stream的更优写法
下面来看一下借助Java 8的Stream API,修改后的代码:

public class Test {

    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list, "张三", "李四", "王五", "张小明", "张三丰");

        list.stream().filter(s -> s.startsWith("张"))
                .filter(s -> s.length() == 3)
                .forEach(System.out::println);
    }
}

直接阅读代码的字面意思即可完美展示无关逻辑方式的语义:获取流、过滤姓张、过滤长度为3、逐一打印
我们真正要做的事情内容被更好地体现在代码中。
Stream流式思想概述
注意:Stream和IO流(InputStream/OutputStream)没有任何关系,请暂时忘记对传统IO流的固有印象!
Stream流式思想类似于工厂车间的“生产流水线”,Stream流不是一种数据结构,不保存数据,而是对数据进行加工处理。
Stream可以看作是流水线上的一个工序。
在流水线上,通过多个工序让一个原材料加工成一个商品。
Stream API能让我们快速完成许多复杂的操作,如筛选、切片、映射、查找、去除重复,统计,匹配和归约。

2、获取Stream流的两种方式

java.util.stream.Stream 是JDK 8新加入的流接口。
获取一个流非常简单,有以下几种常用的方式:

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

方式1 : 根据Collection获取流

首先, java.util.Collection 接口中加入了default方法 stream 用来获取流,所以其所有实现类均可获取流。
21.png

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

        // Map
        Map<String,Object> map = new HashMap<>();
        // 获取流
        Stream<String> stream1 = map.keySet().stream();
        Stream<Object> stream2 = map.values().stream();
        Stream<Map.Entry<String, Object>> stream3 = map.entrySet().stream();
    }
}

差不多就是这个样亚子吧。
注:

java.util.Map 接口不是 Collection 的子接口,所以获取对应的流需要分key、value或entry等情况:

方式2 : Stream中的静态方法of获取流

由于数组对象不可能添加默认方法,所以 Stream 接口中提供了静态方法 of ,使用很简单:

public class Test {

    public static void main(String[] args) {

        String[] arr = {"aa", "bb", "cc"};
        Stream<String> stream = Stream.of(arr);

        Integer[] arr2 = {11, 22, 33};
        Stream<Integer> stream2 = Stream.of(arr2);
        
    }
}

注意:基本数据类型的数组不行
22.png

注: of 方法的参数其实是一个可变参数,所以支持数组。

3、Stream常用方法和注意事项

3.1 Stream常用方法

Stream流模型的操作很丰富,这里介绍一些常用的API。
这些方法可以被分成两种:

方法名 方法作用 返回值类型 方法种类
count 统计个数 long 终结
forEach 逐一处理 void 终结
fifilter 过滤 Stream 函数拼接
limit 取用前几个 Stream 函数拼接
skip 跳过前几个 Stream 函数拼接
map 映射 Stream 函数拼接
concat 组合 Stream 函数拼接
  • 终结方法:返回值类型不再是 Stream 类型的方法,不再支持链式调用。终结方法包括 count 和forEach 方法。
  • 非终结方法:返回值类型仍然是 Stream 类型的方法,支持链式调用。(除了终结方法外,其余方法均为非终结方法。)

更多方法,请自行参考API文档。

3.2 Stream注意事项(重要)

  1. Stream只能操作一次 。
  2. Stream方法返回的是新的流 。
  3. Stream不调用终结方法,中间的操作不会执行。

4、Stream流的forEach(遍历)方法

forEach 用来遍历流中的数据
15.png
该方法接收一个 Consumer 接口函数,会将每一个流元素交给该函数进行处理。例如:

public class Test {
    public static void main(String[] args) {
        List<String> one = new ArrayList<>();
        Collections.addAll(one, "张三", "李四", "王五", "张小明", "张二狗", "张三丰");
        one.stream().forEach(System.out::println);
    }
}

5、Stream流的count(计数)方法

Stream流提供 count 方法来统计其中的元素个数:
16.png
该方法返回一个long值代表元素个数。基本使用:

public class Test {
    public static void main(String[] args) {
        List<String> one = new ArrayList<>();
        Collections.addAll(one, "张三", "李四", "王五", "张小明", "张二狗", "张三丰");
        System.out.println(one.stream().count());
    }
}

6、Stream流的fifilter(过滤)方法

fifilter用于过滤数据,返回符合过滤条件的数据
可以通过 filter 方法将一个流转换成另一个子集流。方法声明:
17.png
该接口接收一个 Predicate 函数式接口参数(可以是一个Lambda或方法引用)作为筛选条件。
Stream流中的 filter 方法基本使用的代码如:

public class Test {
    public static void main(String[] args) {
        List<String> one = new ArrayList<>();
        Collections.addAll(one, "张三", "李四", "王五", "张小明", "张二狗", "张三丰");
        one.stream().filter(s -> s.length() < 3).forEach(System.out::println);
    }
}

在这里通过Lambda表达式来指定了筛选的条件:姓名长度为少于3个字。

7、Stream流的limit(截取)方法

limit 方法可以对流进行截取,只取用前n个。方法签名:
18.png
参数是一个long型,如果集合当前长度大于参数则进行截取。否则不进行操作。基本使用:

public class Test {
    public static void main(String[] args) {
        List<String> one = new ArrayList<>();
        Collections.addAll(one, "张三", "李四", "王五", "张小明", "张二狗", "张三丰");
        one.stream().limit(4).forEach(System.out::println);
    }
}

8、Stream流的skip(截取)方法

如果希望跳过前几个元素,可以使用 skip 方法获取一个截取之后的新流:
19.png
如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流。基本使用:

public class Test {
    public static void main(String[] args) {
        List<String> one = new ArrayList<>();
        Collections.addAll(one, "张三", "李四", "王五", "张小明", "张二狗", "张三丰");
        one.stream().skip(2).forEach(System.out::println);
    }
}

9、Stream流的map(映射)方法

如果需要将流中的元素映射到另一个流中,可以使用 map 方法。方法签名:
20.png
该接口需要一个 Function 函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流。
Stream流中的 map 方法基本使用的代码如:

public class Test {
    public static void main(String[] args) {
        Stream<String> original = Stream.of("11", "22", "33");
        Stream<Integer> result = original.map(Integer::parseInt);
        result.forEach(s -> System.out.println(s + 44));
    }
}

这段代码中, map 方法的参数通过方法引用,将字符串类型转换成为了int类型(并自动装箱为 Integer 类对象)。

10、Stream流的sorted(排序)方法

如果需要将数据排序,可以使用 sorted 方法。方法签名:
21.png
基本使用
Stream流中的 distinct 方法基本使用的代码如:

public class Test {
    public static void main(String[] args) {
        Stream<String> original = Stream.of("11", "22", "33");
        original.map(Integer::parseInt)
                //  根据元素的自然顺序排序
                .sorted()
                // 根据比较器指定的规则排序
                .sorted((o1, o2) -> o2 - o1)
                .forEach(System.out::println);
    }
}

这段代码中,map 方法的参数通过方法引用,将字符串类型转换成为了int类型, sorted 方法根据元素的自然顺序排序,也可以指定比较器排序。

11、Stream流的distinct(去重)方法

如果需要去除重复数据,可以使用 distinct 方法。方法签名:
22.png
基本使用
Stream流中的 distinct 方法基本使用的代码如:

public class Test {
    public static void main(String[] args) {
        Stream<String> original = Stream.of("11", "22", "33","11","22","33");
        original.distinct().forEach(System.out::println);
    }
}

如果是自定义类型如何是否也能去除重复的数据呢?我们来试试看
23.png
自定义类型是根据对象的hashCode和equals来去除重复元素的。

12、Stream流的match(匹配)方法

如果需要判断数据是否匹配指定的条件,可以使用 Match 相关方法。方法签名:
28.png
基本使用
Stream流中的 Match 相关方法基本使用的代码如:
24.png
基本使用
Stream流中的 Match 相关方法基本使用的代码如:
allMatch: 元素是否全部满足条件
24.png
anyMatch: 元素是否任意有一个满足条件
26.png
noneMatch: 元素是否全部不满足条件
27.png

13、Stream流的fifind(找第一)方法

如果需要找到某些数据,可以使用 find 相关方法,它们俩都是找第一个元素。方法签名:、
29.png
基本使用
Stream流中的 find 相关方法基本使用的代码如
30.png

14、Stream流的max和min(大和小)方法

如果需要获取最大和最小值,可以使用 max 和 min 方法。方法签名:
1.png
基本使用
Stream流中的 max 和 min 相关方法基本使用的代码如:
31.png
15、Stream流的reduce(归纳)方法
如果需要将所有数据归纳得到一个数据,可以使用 reduce 方法。方法签名:
2.png
基本使用
Stream流中的 reduce 相关方法基本使用的代码如:
3.png

15、Stream流的map和reduce组合使用

public class Test {
    public static void main(String[] args) {
        // 求出所有年龄的总和
        int totalAge = Stream.of(new Person("小明", 58, 178),
                new Person("小黄", 56, 177),
                new Person("小红", 54, 168)
        ).map((p) -> p.getAge()).reduce(0, Integer::sum);
        // 打印
        System.out.println("所有年龄的总和:" + totalAge);

        // 找出最大年龄
        int AgeMax = Stream.of(new Person("小明", 58, 178),
                new Person("小黄", 56, 177),
                new Person("小红", 54, 168)
        ).map((p) -> p.getAge()).reduce(0, (x, y) -> x > y ? x : y);
        //打印
        System.out.println("最大年龄:" + AgeMax);

        // 统计 数字2 出现的次数
        int count = Stream.of(1, 2, 2, 1, 3, 2).map(i -> {
            if (i == 2) {
                return 1;
            } else {
                return 0;
            }
        }).reduce(0, Integer::sum);
        System.out.println("数字2 出现的次数:" + count);
    }
}

4.png

16、Stream流的mapToInt(转换Int)

如果需要将Stream中的Integer类型数据转成int类型,可以使用 mapToInt 方法。方法签名:
6.png
基本使用
Stream流中的 mapToInt 相关方法基本使用的代码如:
5.png

17、Stream流的concat(合并)方法

如果有两个流,希望合并成为一个流,那么可以使用 Stream 接口的静态方法 concat :
7.png

备注:这是一个静态方法,与 java.lang.String 当中的 concat 方法是不同的。

该方法的基本使用代码如:
8.png

相关文章
|
1月前
|
Java
让星星⭐月亮告诉你,jdk1.8 Java函数式编程示例:Lambda函数/方法引用/4种内建函数式接口(功能性-/消费型/供给型/断言型)
本示例展示了Java中函数式接口的使用,包括自定义和内置的函数式接口。通过方法引用,实现对字符串操作如转换大写、数值转换等,并演示了Function、Consumer、Supplier及Predicate四种主要内置函数式接口的应用。
25 1
|
2月前
|
容器
jdk8新特性-详情查看文档
jdk8新特性-详情查看文档
45 3
|
1月前
|
存储 安全 Java
JDK1.8 新的特性
JDK1.8 新的特性
19 0
|
2月前
|
编解码 安全 Java
jdk8新特性-接口和日期处理
jdk8新特性-接口和日期处理
|
3月前
|
API
JDK8的stream有求和方法吗?
【8月更文挑战第20天】JDK8的stream有求和方法吗?
127 3
|
3月前
|
Java
JDK序列化原理问题之Hessian框架不支持writeObject/readObject方法如何解决
JDK序列化原理问题之Hessian框架不支持writeObject/readObject方法如何解决
|
3月前
|
Oracle Java 关系型数据库
JDK8到JDK29版本升级的新特性问题之未来JDK的升级是否会成为必然趋势,如何理解
JDK8到JDK29版本升级的新特性问题之未来JDK的升级是否会成为必然趋势,如何理解
|
3月前
|
Oracle 安全 Java
JDK8到JDK28版本升级的新特性问题之在Java 15及以后的版本中,密封类和密封接口是怎么工作的
JDK8到JDK28版本升级的新特性问题之在Java 15及以后的版本中,密封类和密封接口是怎么工作的
|
2月前
|
Java 编译器 API
JDK8新特性--lambda表达式
JDK8的Lambda表达式是Java语言的一大进步。它为Java程序提供了更多的编程方式,让代码更加简洁,也让函数式编程的概念在Java中得到了体现。Lambda表达式与Java 8的其他新特性,如Stream API、新的日期时间API一起,极大地提高了Java编程的效率和乐趣。随着时间的流逝,Java开发者对这些特性的理解和应用将会越来越深入,进一步推动Java语言和应用程序的发展。
14 0
|
3月前
|
算法 Java iOS开发
JDK8到JDK27版本升级的新特性问题之JDK 17中G1在资源占用方面有何变化
JDK8到JDK27版本升级的新特性问题之JDK 17中G1在资源占用方面有何变化