Java——Stream流详解

简介: Stream流是JDK 8引入的概念,用于高效处理集合或数组数据。其API支持声明式编程,操作分为中间操作和终端操作。中间操作包括过滤、映射、排序等,可链式调用;终端操作则完成数据处理,如遍历、收集等。Stream流简化了集合与数组的操作,提升了代码的简洁性

Stream流是JDK 8引入的一个概念,它提供了一种高效且表达力强的方式来处理数据集合(如List、Set等)或数组。Stream API可以以声明性方式(指定做什么)来处理数据序列。流操作可以被分为两大类:中间操作(Intermediate Operations)和终端操作(Terminal Operations)。

🍁1. Stream流的适用对象

先得到一条Stream流,并把数据放上去,再进行中间操作和终端操作

获取方式

方法名

说明

单列集合

default Stream<E>stream()

Collection中的默认方法

双列集合

无法直接使用Stream流

数组

public static <T> Stream<T> stream(T[] array)

Arrays工具类中的静态方法

一堆零散数据

public static <T> Stream<T> of(T...values)

Stream接口中的静态方法

先来看单列集合的例子:

public class Demo1 {
    public static void main(String[] args) {
        ArrayList<String> arrayList = new ArrayList<>();
        Collections.addAll(arrayList, "a", "b", "c", "d", "e");
        // 获取Stream流,把集合中的数据放到流上
        Stream<String> stream1 = arrayList.stream();
        //内部类方式打印
        stream1.forEach(new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.print(s);
            }
        });
        System.out.println();
        //获取流之后直接通过lambda表达式进行打印
        arrayList.stream().forEach(s -> System.out.print(s));
    }
}

对于双列集合,需要通过keySet() 或 entrySet() 转化为单列集合,再获取Stream

public class Demo2 {
    public static void main(String[] args) {
        HashMap<String,Integer> hashMap = new HashMap<>();
        hashMap.put("aaa",1);
        hashMap.put("bbb",2);
        hashMap.put("ccc",3);
        hashMap.put("ddd",4);
        //双列集合第一种获取Stream流
        hashMap.keySet().stream().forEach(s -> System.out.print(s + " "));
        System.out.println();
        //双列集合第二种获取
        hashMap.entrySet().stream().forEach(s -> System.out.print(s + " "));
    }
}

数组获取Stream流:

public class Demo3 {
    public static void main(String[] args) {
        int[] arr1 = {1, 2, 3, 4, 5};
        String[] arr2 = {"a", "b", "c", "d"};
        //获取Stream流
        Arrays.stream(arr1).forEach(s-> System.out.print(s + " "));
        System.out.println();
        //数组中是引用型数据类型也可以获取Stream流
        Arrays.stream(arr2).forEach(s -> System.out.print(s + " "));
    }
}

对于不存储在数组或集合中的零散数据,可以直接通过Stream接口中的静态方法获取

public class Demo4 {
    public static void main(String[] args) {
        Stream.of(1,2,3,4,5).forEach(s-> System.out.print(s + " "));
        System.out.println();
        Stream.of("a","b","c").forEach(s-> System.out.print(s + " "));
    }
}

🍁2. 中间方法

中间操作:中间操作可以返回流本身,因此可以链式调用多个中间操作,中间操作可以是对流的过滤(如filter)、映射(如map)、排序(如sorted)等

在上面的中间方法时,只会修改Stream流中的数据,不会影响原来集合或数组中的数据,并且原来的流只能使用一次

🍁2.1 filter()

filter 的参数 Predicate 是一个函数式接口 ,所以可以先使用匿名内部类的方式,再用 lambda 表达式

public class Demo5 {
    public static void main(String[] args) {
        ArrayList<String> arrayList = new ArrayList<>();
        Collections.addAll(arrayList, "aaa", "abc", "acb", "aa", "bb", "cc");
        //fitlter 过滤 把a开头的留下,其余数据不要
        arrayList.stream().filter(new Predicate<String>() {
            @Override
            public boolean test(String s) {
                //返回值为true,当前数据留下,返回值为false,当前数据舍弃
                return s.startsWith("a");
            }
        }).forEach(s -> System.out.print(s + " "));
        System.out.println();
        // lambda表达式的形式
        arrayList.stream().filter(s -> s.startsWith("a")).forEach(s -> System.out.print(s + " "));
        System.out.println();
        //把stream流暂时接收的方式
        Stream<String> stream1 = arrayList.stream().filter(s -> s.startsWith("a"));
        stream1.forEach(s -> System.out.print(s + " "));
    }
}

由于Stream流只能用一次,如果之前的流已经被使用过,再次使用就会报错

所以说,由于只能使用一次,再用一个变量取接收也没有什么意义,直接使用链式编程就可以了

并且,使用流之后,原来集合中的元素也不会改变

🍁2.2 limit() 和 skip()

/* limit() 获取前面几个元素
            skip() 跳过几个元素*/
        arrayList.stream().limit(3).forEach(s -> System.out.print(s + " "));
        System.out.println();
        arrayList.stream().skip(3).forEach(s -> System.out.print(s + " "));

🍁2.3 distinct()和concat()

distinct()去重是依赖hashCode()和equals()方法的,所以如果是自定义类型,要手动的重写这两个方法

public class Demo6 {
    public static void main(String[] args) {
        ArrayList<String> arrayList1 = new ArrayList<>();
        Collections.addAll(arrayList1, "aaa", "aaa", "acb", "aa", "bb", "cc");
        ArrayList<String> arrayList2 = new ArrayList<>();
        Collections.addAll(arrayList2,"dd","ee");
        //去重
        arrayList1.stream().distinct().forEach(s -> System.out.print(s + " "));
        System.out.println();
        //流的合并
        Stream.concat(arrayList1.stream(),arrayList2.stream()).forEach(s -> System.out.print(s + " "));
    }
}

🍁2.4 map()

需求:获取集合中的数字部分

首先调用split方法进行分割,得到数字部分之后再转换为整型

public class Demo7 {
    public static void main(String[] args) {
        ArrayList<String> arrayList = new ArrayList<>();
        Collections.addAll(arrayList, "aaa-11", "acb-22", "aa-33", "bb-44", "cc-55");
        // 获取集合中的数字部分
        arrayList.stream().map(new Function<String, Integer>() {
            @Override
            public Integer apply(String s) {
                //使用split进行分割
                String[] arr = s.split("-");
                //获取数字部分
                String num = arr[1];
                //转换为int类型
                int ans = Integer.parseInt(num);
                return ans;
            }
        }).forEach(s -> System.out.print(s + " "));
        System.out.println();
        //lambda表达式
        arrayList.stream()
                .map(s -> Integer.parseInt(s.split("-")[1]))
                .forEach(s -> System.out.print(s + " "));
    }
}

🍁3. 终结方法

名称

说明

void forEach(Consumer action)

遍历

long count()

统计

toArray()

收集流中的数据,放到数组中

collect(Collector collector)

收集流中的数据,放到集合中

🍁3.1 forEach()和count()

forEach方法在之前已经演示过了,就是进行遍历的

forEach中的参数Consumer也是一个函数式接口,也可以先使用匿名内部类的方式,再用 lambda 表达式

public class Demo8 {
    public static void main(String[] args) {
        ArrayList<String> arrayList = new ArrayList<>();
        Collections.addAll(arrayList, "aaa", "aaa", "acb", "aa", "bb", "cc");
        arrayList.forEach(new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.print(s + " ");
            }
        });
        System.out.println();
        arrayList.forEach(s-> System.out.print(s + " "));
        System.out.println();
        //统计个数
        long l = arrayList.stream().count();
        System.out.println(l);
    }
}

count() 的返回值为long类型的,可以定义一个变量进行接收

🍁3.2 toArray()

toArray()是收集流里面的数据放在数组中

toArray()方法有两种返回类型,一种是Object类型的,另一种是指定类型的,Object类型的比较简单,直接用Object类型的变量来接收就可以了

public class Demo9 {
    public static void main(String[] args) {
        ArrayList<String> arrayList = new ArrayList<>();
        Collections.addAll(arrayList, "aaa", "aaa", "acb", "aa", "bb", "cc");
        Object[] array1 = arrayList.stream().toArray();
        System.out.println(Arrays.toString(array1));
    }
}

接下来看指定类型

toArray方法的参数也是一个函数式接口,还是使用匿名内部类和lambda表达式两种方式演示

public class Demo9 {
    public static void main(String[] args) {
        ArrayList<String> arrayList = new ArrayList<>();
        Collections.addAll(arrayList, "aaa", "aaa", "acb", "aa", "bb", "cc");
        
        //IntFunction的泛型:具体类型的数组
        //apply方法的形参:流中数据的个数,返回的数组的长度要一致
        //返回值就是具体类型的数组
        String[] array2 = arrayList.stream().toArray(new IntFunction<String[]>() {
            @Override
            public String[] apply(int value) {
                return new String[value];
            }
        });
        System.out.println(Arrays.toString(array2));
        //lambda表达式
        String[] array = arrayList.stream().toArray(value -> new String[value]);
        System.out.println(Arrays.toString(array));
    }
}

IntFunction的泛型是具体类型的数组
apply方法的形参表示流中数据的个数,返回的数组的长度要一致,最后的返回值就是具体类型的数组

🍁3.3 collect()

collect() 方法就是收集流里面的数据放到集合中,下面先来演示收集到List和Set集合中的例子

public class Demo10 {
    public static void main(String[] args) {
        ArrayList<String> arrayList = new ArrayList<>();
        Collections.addAll(arrayList, "张三-男-23", "李四-男-21", "王五-女-22", "钱七-女-22");
        // 把所有男性收集起来
        //收集到List集合中
        List<String> list = arrayList.stream()
                .filter(s -> "男".equals(s.split("-")[1]))
                .collect(Collectors.toList());
        //收集到Set集合中
        Set<String> set = arrayList.stream()
                .filter(s -> "男".equals(s.split("-")[1]))
                .collect(Collectors.toSet());
    }
}

收集到List和Set集合中的数据的区别还是和List和Set集合的区别一样的,Set集合中不能有重复的元素,如果流中收集的数据存在重复的数据,在收集到Set集合之后就会进行去重

接下来看Map集合,由于Map集合是一个双列集合,所以需要指定键和值的生成规则

这里的生成规则比较复杂

//收集到Map集合中
        /**
         * toMap : 参数一表示键的生成规则
         *         参数二表示值的生成规则
         * 参数一:
         * Function 泛型一:表示流中的数据类型
         *          泛型二:表示Map集合中键的类型
         * 方法 apply 形参:表示流中的每一个数据
         *           方法体:生成键的代码
         *           返回值:已经生成的键
         *
         */
        Map<String, Integer> map1 = arrayList.stream()
                .filter(s -> "男".equals(s.split("-")[1]))
                .collect(Collectors.toMap(new Function<String, String>() {
                                              @Override
                                              public String apply(String s) {
                                                  return s.split("-")[0];
                                              }
                                          }, new Function<String, Integer>() {
                                              @Override
                                              public Integer apply(String s) {
                                                  return Integer.parseInt(s.split("-")[2]);
                                              }
                                          }
                ));
        System.out.println(map1);
//lambda表达式
        Map<String, Integer> map2 = arrayList.stream()
                .filter(s -> "男".equals(s.split("-")[1]))
                .collect(Collectors.toMap(s -> s.split("-")[0], s -> Integer.parseInt(s.split("-")[2])));
        System.out.println(map2);

由于Map集合中是不能有重复的键的,所以如果Stream流获取的键中存在了重的元素,就会报错

🍁4. Stream流的作用和使用步骤总结

作用:Stream流就是结合了lambda表达式,简化集合和数组的操作

使用步骤:

1. 获取Stream流对象

2.使用中间方法处理数据

3.使用终结方法处理数据

相关文章
|
3月前
|
安全 Java API
告别繁琐编码,拥抱Java 8新特性:Stream API与Optional类助你高效编程,成就卓越开发者!
【8月更文挑战第29天】Java 8为开发者引入了多项新特性,其中Stream API和Optional类尤其值得关注。Stream API对集合操作进行了高级抽象,支持声明式的数据处理,避免了显式循环代码的编写;而Optional类则作为非空值的容器,有效减少了空指针异常的风险。通过几个实战示例,我们展示了如何利用Stream API进行过滤与转换操作,以及如何借助Optional类安全地处理可能为null的数据,从而使代码更加简洁和健壮。
104 0
|
7天前
|
Java API 数据处理
探索Java中的Lambda表达式与Stream API
【10月更文挑战第22天】 在Java编程中,Lambda表达式和Stream API是两个强大的功能,它们极大地简化了代码的编写和提高了开发效率。本文将深入探讨这两个概念的基本用法、优势以及在实际项目中的应用案例,帮助读者更好地理解和运用这些现代Java特性。
|
28天前
|
Java 流计算
Flink-03 Flink Java 3分钟上手 Stream 给 Flink-02 DataStreamSource Socket写一个测试的工具!
Flink-03 Flink Java 3分钟上手 Stream 给 Flink-02 DataStreamSource Socket写一个测试的工具!
34 1
Flink-03 Flink Java 3分钟上手 Stream 给 Flink-02 DataStreamSource Socket写一个测试的工具!
|
28天前
|
Java Shell 流计算
Flink-02 Flink Java 3分钟上手 Stream SingleOutputStreamOpe ExecutionEnvironment DataSet FlatMapFunction
Flink-02 Flink Java 3分钟上手 Stream SingleOutputStreamOpe ExecutionEnvironment DataSet FlatMapFunction
20 1
Flink-02 Flink Java 3分钟上手 Stream SingleOutputStreamOpe ExecutionEnvironment DataSet FlatMapFunction
|
28天前
|
存储 Java 数据处理
Flink-01 介绍Flink Java 3分钟上手 HelloWorld 和 Stream ExecutionEnvironment DataSet FlatMapFunction
Flink-01 介绍Flink Java 3分钟上手 HelloWorld 和 Stream ExecutionEnvironment DataSet FlatMapFunction
27 1
|
2月前
|
Java API C++
Java 8 Stream Api 中的 peek 操作
本文介绍了Java中`Stream`的`peek`操作,该操作通过`Consumer&lt;T&gt;`函数消费流中的每个元素,但不改变元素类型。文章详细解释了`Consumer&lt;T&gt;`接口及其使用场景,并通过示例代码展示了`peek`操作的应用。此外,还对比了`peek`与`map`的区别,帮助读者更好地理解这两种操作的不同用途。作者为码农小胖哥,原文发布于稀土掘金。
101 9
Java 8 Stream Api 中的 peek 操作
|
2月前
|
Java C# Swift
Java Stream中peek和map不为人知的秘密
本文通过一个Java Stream中的示例,探讨了`peek`方法在流式处理中的应用及其潜在问题。首先介绍了`peek`的基本定义与使用,并通过代码展示了其如何在流中对每个元素进行操作而不返回结果。接着讨论了`peek`作为中间操作的懒执行特性,强调了如果没有终端操作则不会执行的问题。文章指出,在某些情况下使用`peek`可能比`map`更简洁,但也需注意其懒执行带来的影响。
119 2
Java Stream中peek和map不为人知的秘密
|
2月前
|
Java 大数据 API
Java 流(Stream)、文件(File)和IO的区别
Java中的流(Stream)、文件(File)和输入/输出(I/O)是处理数据的关键概念。`File`类用于基本文件操作,如创建、删除和检查文件;流则提供了数据读写的抽象机制,适用于文件、内存和网络等多种数据源;I/O涵盖更广泛的输入输出操作,包括文件I/O、网络通信等,并支持异常处理和缓冲等功能。实际开发中,这三者常结合使用,以实现高效的数据处理。例如,`File`用于管理文件路径,`Stream`用于读写数据,I/O则处理复杂的输入输出需求。
|
2月前
|
Java 程序员 API
Java 8新特性之Lambda表达式与Stream API的探索
【9月更文挑战第24天】本文将深入浅出地介绍Java 8中的重要新特性——Lambda表达式和Stream API,通过实例解析其语法、用法及背后的设计哲学。我们将一探究竟,看看这些新特性如何让Java代码变得更加简洁、易读且富有表现力,同时提升程序的性能和开发效率。
|
2月前
|
SQL Java Linux
Java 8 API添加了一个新的抽象称为流Stream
Java 8 API添加了一个新的抽象称为流Stream