Stream流

Java 8 是一个非常成功的版本,这个版本新增的Stream,配合同版本出现的 Lambda ,给我们操作集合(Collection)提供了极大的便利。

一、引言

1.1 传统集合的多步遍历代码

对于集合的操作,除了添加、删除、获取外,最常用的就是集合遍历。

  • 需求:打印姓名为张且名字为3个字的人的名字
  • 集合元素:张无忌、周芷若、赵敏、张强、张三丰

在 java 8 之前做法可能为

public class Demo01List {
    public static void main(String[] args) {
        //创建一个List集合,存储姓名
        List<String> list = new ArrayList<>();
        list.add("张无忌");
        list.add("周芷若");
        list.add("赵敏");
        list.add("张强");
        list.add("张三丰");

        //对list集合中的元素进行过滤,只要以张开头的元素,存储到一个新的集合中
        List<String> listA = new ArrayList<>();
        for(String s : list){
            if(s.startsWith("张")){
                listA.add(s);
            }
        }

        //对listA集合进行过滤,只要姓名长度为3的人,存储到一个新集合中
        List<String> listB = new ArrayList<>();
        for (String s : listA) {
            if(s.length()==3){
                listB.add(s);
            }
        }

        //遍历listB集合
        for (String s : listB) {
            System.out.println(s);
        }
    }
}

这段代码中含有三个循环,每一个作用不同:

  1. 首先筛选所有姓张的人;

  2. 然后筛选名字有三个字的人;

  3. 最后进行对结果进行打印输出。

每当我们需要对集合中的元素进行操作的时候,总是需要进行循环、循环、再循环。这是理所当然的么?不是循环只是做事情的方式,而不是目的。另一方面,使用线性循环就意味着只能遍历一次。如果希望再次遍历,只能再使用另一个循环从头开始。 这是一种远不够高效、笨拙的方法。

1.2 Stream写法

关注的是做什么,而不是怎么做,借助Java 8的Stream API对流中的元素进行操作,代码更加简洁易读,而且使用并发模式,程序执行速度更快。

public class Demo02Stream {
    public static void main(String[] args) {
        //创建一个List集合,存储姓名
        List<String> list = new ArrayList<>();
        list.add("张无忌");
        list.add("周芷若");
        list.add("赵敏");
        list.add("张强");
        list.add("张三丰");

        //对list集合中的元素进行过滤,只要以张开头的元素,存储到一个新的集合中
        //对listA集合进行过滤,只要姓名长度为3的人,存储到一个新集合中
        //遍历listB集合
        list.stream()
                .filter(name->name.startsWith("张"))
            .filter(name->name.length()==3)
            .forEach(name-> System.out.println(name));
    }
}

直接阅读代码的字面意思即可完美展示无关逻辑方式的语义:获取流、过滤姓张、过滤长度为3、逐一打印。代码中并没有体现使用线性循环或是其他任何算法进行遍历,我们真正要做的事情内容被更好地体现在代码中。

二、Stream概念

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

  • Stream(流)是数据渠道,用于操作数据源(集合、数组等)所生成的元素(特定类型的对象)序列。“集合讲的是数据,流讲的是计算!”

    注意:

    1. Stream 其实是一个集合元素的函数模型,并不是集合元素,也不是数据结构,其本身并不存储任何元素(或其地址值),它是有关算法和计算的
    2. 数据源本身可以是无限的
    3. Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
    4. Stream操作是延迟执行的。这意味着他们会等到需要结果的时候才执行
  • 和以前的Collection操作不同,Stream操作还有两个基础的特征:

    1. Pipelining :中间操作都会返回流对象本身。这样多个操作可以串联成一个管道,如同流式风格(fluent style)。这样做可以对操作进行优化,比如延迟执行(laziness)短路(short-circuiting)
    2. 内部迭代 :以前对集合遍历都是通过Iterator或者增强for的方式,显式的在集合外部进行迭代,这叫做外部迭代Stream 提供了内部迭代的方式,流可以直接调用遍历方法。

三、Stream操作步骤

当我们使用一个流的时候,通常包括三个基本步骤:

  1. 创建Stream:获取一个数据源(如:集合、数组)
  2. 中间操作:一个中间操作链,对数据源的数据进行处理【数据转换】
  3. 终止操作(终端操作):一个终止操作,执行中间操作链,并产生结果

注意:每次转换原有Stream对象不改变,返回一个新的Stream对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道,如下图所示。

四、创建Stream

java.util.stream.Stream<T> 是Java 8新加入的最常用的流接口。(这并不是一个函数式接口。)

获取一个流非常简单,有以下几种常用的方式:

  • 所有的 Collection 集合都可以通过 stream 默认方法获取流;

    • default Stream<E> stream(): 返回一个顺序流
    • default Stream<E> parallelStream() : 返回一个并行流
  • Stream 接口的静态方法 of 可以获取数组对应的流。

    static <T> Stream<T> of(T... values)
    参数是一个可变参数,那么我们就可以传递一个数组

  • Arrays中的静态方法stream()获取数组流

    static <T> Stream<T> stream(T[] array) : 返回顺序Stream与指定的数组作为源

4.1 根据Collection获取流

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

import java.util.*; 
import java.util.stream.Stream; 
public class Demo04GetStream { 
    public static void main(String[] args) {
        List<String> list = new ArrayList<>(); 
        // stream()
		Stream<String> stream1 = list.stream();
		// parallelStream()
		Stream<String> parallelStream1 = list.parallelStream();
        
        Set<String> set = new HashSet<>();
        // stream()
        Stream<String> stream2 = set.stream(); 
        // parallelStream()
		Stream<String> parallelStream2 = set.parallelStream();
        
        Vector<String> vector = new Vector<>();
        // stream()
        Stream<String> stream3 = vector.stream();
        // parallelStream()
		Stream<String> parallelStream3 = vector.parallelStream();
    }
}

4.2 根据Map获取流

java.util.Map 接口不是 Collection 的子接口,且其 K-V 数据结构不符合流元素的单一特征,所以获取对应的流需要分 keyvalueentry 等情况:

import java.util.HashMap; 
import java.util.Map; 
import java.util.stream.Stream; 
public class Demo05GetStream { 
    public static void main(String[] args) { 
        Map<String, String> map = new HashMap<>(); 
        // ... 
        Stream<String> keyStream = map.keySet().stream(); 
        Stream<String> valueStream = map.values().stream(); 
        Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream();
    }
}

4.3 根据数组获取流

如果使用的不是集合或映射而是数组,由于数组对象不可能添加默认方法,所以 Stream

接口中提供了静态方法 of ,还有Arrays数组工具类也提供了静态方法 stream 来获取流,使用方法也很简单:

import java.util.stream.Stream; 
public class Demo06GetStream {
    public static void main(String[] args) { 
        String[] array = {"张三", "李四", "王五", "赵六", "田七"}; 
        // of
        Stream<String> stream1 = Stream.of(array); 
        // stream
        Stream<String> stream2 = Arrays.stream(array);
    } 
}

五、中间操作

多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值”。

中间操作常用方法:

  • filter(): 对元素进行过滤
  • map():元素映射,用于类型转换
  • limit():用于截取流中的元素
  • skip():用于跳过元素
  • sorted():对元素排序
  • distinct():去除重复的元素

5.1 过滤:filter

  • 可以通过 filter 方法将一个流转换成另一个子集流。方法签名:

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

    该接口接收一个 Predicate 函数式接口参数(可以是一个Lambda或方法引用)作为筛选条件。

  • Predicate中的抽象方法 :
    boolean test(T t);

    该方法将会产生一个 boolean 值结果,代表指定的条件是否满足。如果结果为 true,那么 Stream流的 filter 方法将会留用元素;如果结果为 false,那么 filter 方法将会舍弃元素。

  • 代码演示

    import java.util.stream.Stream;
    
    public class Demo07Stream_filter {
        public static void main(String[] args) {
            //创建一个Stream流
            Stream<String> stream = Stream.of("张三丰", "张翠山", "赵敏", "周芷若", "张无忌");
            //对Stream流中的元素进行过滤,只要姓张的人
            Stream<String> stream2 = stream.filter((String name)->{return name.startsWith("张");});
            //遍历stream2流
            stream2.forEach(name-> System.out.println(name));
    
    
            //遍历stream流 (抛异常)
            stream.forEach(name-> System.out.println(name));
        }
    }
    
    

    注意:

    ​ Stream流属于管道流,只能被消费(使用)一次

    第一个 Stream 流调用完毕方法,数据就会流转到下一个Stream上,而这时第一个 Stream 流已经使用完毕,就会自动关闭了,所以第一个 Stream 流就不能再调用方法了,如果调用就会抛
    IllegalStateException: stream has already been operated upon or closed 异常

5.2 映射:map

  • 将流中的元素映射到另一个流中,使用 map 方法。方法签名:

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

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

  • Function中的抽象方法:
    R apply(T t);

这可以将一种 T 类型转换成为 R 类型,而这种转换的动作,就称为 映射

  • 代码演示:

    import java.util.stream.Stream;
    
    public class Demo08Stream_map {
        public static void main(String[] args) {
            //获取一个String类型的Stream流
            Stream<String> stream = Stream.of("1", "2", "3", "4");
            //使用map方法,把字符串类型的整数,转换(映射)为Integer类型的整数
            Stream<Integer> stream2 = stream.map((String s)->{
                return Integer.parseInt(s);
            });
            //遍历Stream2流
            stream2.forEach(i-> System.out.println(i));
        }
    }
    

5.3 截取:limit

  • limit 方法可以对流进行截取,只取用前n个。方法签名:

    Stream<T> limit(long maxSize);

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

  • 代码演示:

    import java.util.stream.Stream;
    
    public class Demo09Stream_limit {
        public static void main(String[] args) {
            //获取一个Stream流
            String[] arr = {"美羊羊","喜洋洋","懒洋洋","灰太狼","红太狼"};
            Stream<String> stream = Stream.of(arr);
            //使用limit对Stream流中的元素进行截取,只要前3个元素
            Stream<String> stream2 = stream.limit(3);
            //遍历stream2流
            stream2.forEach(name-> System.out.println(name));
        }
    }
    

5.4 跳过:skip

  • skip 方法可以对流进行截取,跳过前n个元素。方法签名:

    Stream<T> skip(long n);

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

  • 代码演示:

    import java.util.stream.Stream;
    
    public class Demo10Stream_skip {
        public static void main(String[] args) {
            //获取一个Stream流
            String[] arr = {"美羊羊","喜洋洋","懒洋洋","灰太狼","红太狼"};
            Stream<String> stream = Stream.of(arr);
            //使用skip方法跳过前3个元素
            Stream<String> stream2 = stream.skip(3);
            //遍历stream2流
            stream2.forEach(name-> System.out.println(name));
        }
    }
    

5.5 排序:sorted

  • sorted 方法可以对流进行排序。方法签名:

    • Stream<T> sorted():返回由此流的元素组成的流,根据自然顺序排序。
    • Stream<T> sorted(Comparator<? super T> comparator) :返回由该流的元素组成的流,根据提供的 Comparator进行排序。
  • 代码演示

    import java.util.Arrays;
    import java.util.List;
    import java.util.stream.Stream;
    
    public class Demo11Stream_sorted {
        public static void main(String[] args) {
    
            List<Integer> list = Arrays.asList(2, 3, 1, 5,4);
            //sorted():产生一个新流,其中按自然顺序排序
            Stream<Integer> stream=list.stream();
            Stream<Integer> stream2 = stream.sorted();
            stream2.forEach(i-> System.out.println(i));
            // sorted(Comparator):产生一个新流,其中按比较器顺序排序
            Stream<Integer> stream3=list.stream();
            Stream<Integer> stream4 = stream3.sorted((param1,param2) -> (param1 < param2 ? 1 : -1 ));
            stream4.forEach(i-> System.out.println(i));
        }
    }
    

5.6 去重:distinct

  • distinct 方法可以对去除流中的重复元素。方法签名:
    Stream<T> distinct():返回该流的不同元素

  • 代码演示

    import java.util.Arrays;
    import java.util.List;
    import java.util.stream.Stream;
    
    public class Demo11Stream_distinct {
        public static void main(String[] args) {
            List<Integer> list = Arrays.asList(2, 2, 1, 4,4);
            //sorted():产生一个新流,其中按自然顺序排序
            Stream<Integer> stream=list.stream();
            Stream<Integer> stream2 = stream.distinct();
            stream2.forEach(i-> System.out.println(i));
        }
    }
    

六、终止操作

执行中间操作链,并产生结果。

常用方法:

  • forEach():遍历每个元素。
  • reduce():把 Stream 元素组合起来。例如,字符串拼接,数值的 sum,min,max ,average 都是特殊的 reduce。
  • concat():合并流
  • min():找到最小值。
  • max():找到最大值。
  • count():返回流中元素总数

6.1 逐一处理:forEach

  • 虽然方法名字叫 forEach ,但是与 for 循环中的“for-each”昵称不同。 方法签名:

    void forEach(Consumer<? super T> action);

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

  • Consumer接口中包含抽象方法:

    void accept(T t),意为消费一个指定泛型的数据。

6.2 元素组合:reduce

这个方法的主要作用是把 Stream元素组合起来。它提供一个起始值(种子),然后依照运算规则(BinaryOperator),和前面Stream的第一个、第二个、第n个元素组合。从这个意义上说,字符串拼接、数值的 sum、min、max、average都是特殊的reduce。例如Stream的sum就相当于:

Integer sum = integers.reduce(0, (a, b) -> a+b);

Integer sum = integers.reduce(0, Integer::sum);

也有 没有起始值的情况,这时会把 Stream 的前面两个元素组合起来,返回的是 Optional

// reduce 的用例

// 字符串连接,concat = "ABCD"
String concat = Stream.of("A", "B", "C", "D").reduce("", String::concat);

// 求最小值,minValue = -3.0
double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MAX_VALUE, Double::min); 

// 求和,sumValue = 10, 有起始值
int sumValue = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum);

// 求和,sumValue = 10, 无起始值
sumValue = Stream.of(1, 2, 3, 4).reduce(Integer::sum).get();

// 过滤,字符串连接,concat = "ace"
concat = Stream.of("a", "B", "c", "D", "e", "F").
 filter(x -> x.compareTo("Z") > 0).
 reduce("", String::concat);123456789101112131415161718

上面代码例如第一个示例的reduce(),第一个参数(空白字符)即为起始值,第二个参数(String::concat)为 BinaryOperator。这类有起始值的 reduce() 都返回具体的对象。而对于第四个示例没有起始值的 reduce(),由于可能没有足够的元素,返回的是一个 Optional 类型的对象,可以通过 get() 方法获得值。

6.3 流组合:concat

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

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

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

public class Demo08Stream_concat {
    public static void main(String[] args) {
        //创建一个Stream流
        Stream<String> stream1 = Stream.of("张三丰", "张翠山", "赵敏", "周芷若", "张无忌");
        //获取一个Stream流
        String[] arr = {"美羊羊","喜洋洋","懒洋洋","灰太狼","红太狼"};
        Stream<String> stream2 = Stream.of(arr);
        //把以上两个流组合为一个流
        Stream<String> concat = Stream.concat(stream1, stream2);
        //遍历concat流
        concat.forEach(name-> System.out.println(name));
    }
}

6.4 最值:max/min

  • Optional<T> max(Comparator<? super T> comparator) :根据提供的 Comparator返回此流的最大元素。
  • Optional<T> min(Comparator<? super T> comparator) :根据提供的 Comparator返回此流的最小元素。
public class Demo12Stream_zuizhi {
    public static void main(String[] args) {

        List<Integer> list = Arrays.asList(2, 3, 1, 5,4);
        Stream<Integer> stream=list.stream();
        Optional min=stream.min((param1,param2) -> (int)param1 > (int)param2 ? 1:-1 );
        System.out.println(min.get());//1

        Stream<Integer> stream2=list.stream();
        Optional max=stream2.max((param1,param2) -> (int)param1 > (int)param2 ? 1:-1 );
        System.out.println(max.get());//5

    }
}

6.5 统计个数:count

正如旧集合 Collection 当中的 size 方法一样,流提供 count 方法来数一数其中的元素个数:

long count();

该方法返回一个 long 值代表元素个数(不再像旧集合那样是int值)。

import java.util.stream.Stream; 
public class Demo13Stream_count { 
    public static void main(String[] args) { 
        Stream<String> original = Stream.of("张无忌", "张三丰", "周芷若"); 
        Stream<String> result = original.filter(s ‐> s.startsWith("张")); 
        System.out.println(result.count()); // 2 
    } 
}

七、练习

现在有两个 ArrayList 集合存储队伍当中的多个成员姓名,Stream流式处理方式进行以

下若干操作步骤:

  1. 第一个队伍只要名字为3个字的成员姓名;存储到一个新集合中。

  2. 第一个队伍筛选之后只要前3个人;存储到一个新集合中。

  3. 第二个队伍只要姓张的成员姓名;存储到一个新集合中。

  4. 第二个队伍筛选之后不要前2个人;存储到一个新集合中。

  5. 将两个队伍合并为一个队伍;存储到一个新集合中。

  6. 根据姓名创建 Person 对象;存储到一个新集合中。

  7. 打印整个队伍的Person对象信息。

代码如下:

person类

public class Person {
    private String name;

    public Person() {
    }

    public Person(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                '}';
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

测试类

import java.util.ArrayList;
import java.util.stream.Stream;

public class StreamTest {
    public static void main(String[] args) {
        //第一支队伍
        ArrayList<String> one = new ArrayList<>();
        one.add("迪丽热巴");
        one.add("宋远桥");
        one.add("苏星河");
        one.add("石破天");
        one.add("石中玉");
        one.add("老子");
        one.add("庄子");
        one.add("洪七公");
        //1. 第一个队伍只要名字为3个字的成员姓名;存储到一个新集合中。
        //2. 第一个队伍筛选之后只要前3个人;存储到一个新集合中。
        Stream<String> oneStream = one.stream().filter(name -> name.length() == 3).limit(3);

        //第二支队伍
        ArrayList<String> two = new ArrayList<>();
        two.add("古力娜扎");
        two.add("张无忌");
        two.add("赵丽颖");
        two.add("张三丰");
        two.add("尼古拉斯赵四");
        two.add("张天爱");
        two.add("张二狗");
        //3. 第二个队伍只要姓张的成员姓名;存储到一个新集合中。
        //4. 第二个队伍筛选之后不要前2个人;存储到一个新集合中。
        Stream<String> twoStream = two.stream().filter(name -> name.startsWith("张")).skip(2);

        //5. 将两个队伍合并为一个队伍;存储到一个新集合中。
        //6. 根据姓名创建Person对象;存储到一个新集合中。
        //7. 打印整个队伍的Person对象信息。
        Stream.concat(oneStream,twoStream).map(name->new Person(name)).forEach(System.out::println);
    }
}

上述采用了链式编程,减少了中间变量,极大的简化了代码量,本文其他地方也可采用链式编程来简化代码。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

江七7

感谢大佬的赏赐

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值