Java-Stream流(基础篇)

1. 概念引入

  • 利用lambda函数式编程,解决已有集合或数组既有的弊端。这个就是Stream流。
  • 优点:
    • 提高编程效率和程序可读性;
    • 支持并发方式处理。

1.1 简述集合循环遍历的弊端

在没有引入Stream流前:

  • 集合的线性遍历是一次性的。如果需要再次遍历,就只能在另一个循环中从头开始遍历。
  • 对集合内部元素的每次操作(比如筛选指定元素)都需要通过遍历的方式。

2. Stream流概述

  1. 与传统意义的流比较:
    • 传统的IO流,就好比运河,将数据从一个地方传送到另外的一个地方。发送/接收的数据基本是一致的。
    • 而Stream流则好比工厂的流水线,通过流水线上的一步步操作,最终获得所需要的“产品”。
  2. 操作Stream流的含义:
    • Stream流本质是一个集合元素的函数模型,不是集合也不是数据结构,本身不存储任何元素/地址值。
    • Stream流的操作实际是对函数模型进行转换、设置,集合元素实际上并未被处理。只有Stream流的终结方法被执行的时候,集合元素才会根据指定的模型进行操作(lambda表达式的延迟执行)。

3. 获得Stream流的方式

  • java.util.stream.Stream<T> Stream流最常用的接口。注意,这个不是函数式接口,别直接用lambda

    1. Collection集合都能通过Collection接口中的默认方法stream获取Stream流:集合名.stream();
    2. Stream接口的静态方法of可以获得数组对应的Stream流:Stream.of(数组名)

3.1 Collection获取流

集合名.stream();

//ArrayList
List<Integer> list = new ArrayList<>();
Stream<Integer> ALscream = list.stream();
//Set
Set<Integer> Set = new HashSet<>();
Stream<Integer> Setscream = Set.stream();

3.2 Map获得流

  • Map不能直接获得对应的流,键值对的数据结构与流的单一特征不相符。
  • Map获得流需要分成获得Key的流、获得Value的流、获得Entry的流三种:
Map<String, String> map = new HashMap<>();
//获得Key的流
Stream<String> keyStream = map.keySet().stream();
//获得Value的流
Stream<String> valueStream = map.values().stream();
//获得Entry的流
Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream();

3.3 数组获得流

Stream.of(数组名)

int[] array = {1,351,3,2132,884,35};
Stream<Integer> stream = Stream.of(array);

4. Stream流常用方法

  • 终结方法:返回值类型不在是Stream接口自身类型的方法,不支持链式调用。可以视为Stream流模型已确定,执行该方法时集合将按照指定模型进行操作。常用的终结方法有:countforeach
  • 非终结方法:返回值类型是Stream接口自身类型的方法,支持链式调用。(除终结方法以外的Stream流方法)

注意事项

  1. Stream流一旦调用终结方法,就会被关闭,不能继续调用方法操作流中的元素!!
  2. 如果通过流方法产生新的流,则旧的流就不能继续操作!!

4.1 filter过滤

  • 转换成另外一个子集流,其方法声明如下

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

    该接口接收Predicate函数式接口参数作为筛选条件,返回true则保留元素,返回false则舍弃元素。

  • 使用格式:集合流名.filter(筛选条件)

    //示例
    String[] strs = {"马云""马化腾","周杰伦"};
    Stream<String> original = Stream.of(str);
    Stream<String> result = original.filter(s ‐> s.startsWith("马"));//用lambda指定筛选条件

4.2 count统计个数

  • 统计元素个数,返回long值
  • 格式:long count();
  • 使用格式:流名.count();

4.3 limit获取前几个

  • 对流进行截取,取出前几个元素放到新流中
  • 格式:Stream<T> limit(long maxSize);
    • 集合当前长度大于参数就截取,当前长度小于参数则会将所有元素存储到新流中。
    • maxSize不能为负数。
  • 使用格式:流名.limit(long maxSize);

4.4 skip跳过前几个

  • 跳过前几个元素,获得截取后的新流
  • 格式:Stream<T> skip(long n);
    • 集合当前长度大于参数就跳过前n个,小于参数返回长度为0的空流。
    • n不能为负数
  • 使用格式:流名.skip(long n);

4.5 map映射

  • 使用map操作可遍历集合中的每个对象并对其操作
  • map之后用调用collect方法可得到操作后的集合
  • 格式:<R> Stream<R> map(Function<? super T, ? extends R> mapper);
    • 需要一个Function函数式接口,将当前流中的T类型数据转换为另一种R类型的流。
  • 映射:一一对应
public static void main(String[] args) {
    Stream<String> original = Stream.of("aa", "bb", "cc");
    Stream<Integer> result = original.map(Integer::parseInt);//<=>original.map(s->Integer.parseInt(s))
}

4.6 concat组合

  • 两个流合并成一个流,使用Stream流的静态方法concat方法

  • 格式:static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)

  • 使用格式:Stream.concat(StreamA,StreamB);

    注意:合并之后,原来的两个流就不能再使用

4.7 foreach逐一处理

  • 不是for循环中的增强for(for-each)
  • 元素的逐一消费(操作)
  • 格式:void forEach(Consumer<? super T> action);
    • 需要一个Consumer接口,将流中每个元素交由其处理
Stream<String> stream = Stream.of("马云","马化腾","周杰伦");
stream.forEach(System.out::println);//<=>s->System.out.println(s);

4.8 示例案例

public class DemoTest {
    public static void main(String[] args) {
        List<String> one = new ArrayList<>();
        Collections.addAll(one,"迪丽热巴","宋远桥","苏星河","老子","庄子","孙子","洪七公");
        List<String> two = new ArrayList<>();
        Collections.addAll(two,"古力娜扎","张无忌","张三丰","赵丽颖","张二狗","张天爱","张三");
        //1. 第一个队伍只要名字为3个字的成员姓名;
        //2. 第一个队伍筛选之后只要前3个人;
        Stream<String> stream1 = one.stream().filter(s -> s.length() == 3).limit(3);
        //3. 第二个队伍只要姓张的成员姓名;
        //4. 第二个队伍筛选之后不要前2个人;
        Stream<String> stream2 = two.stream().filter(s -> s.startsWith("张")).skip(2);
        //5. 将两个队伍合并为一个队伍;
        Stream<String> stream = Stream.concat(stream1, stream2);
        Stream<Person> finalStream = stream.map(Person::new);//用map方法转换类型
        finalStream.forEach(System.out::println);
        /*上述三行代码可以直接合并成
        * Stream.concat(stream1, stream2).map(Person::new).forEach(System.out::println); */
    }
}

public class Person {
    private String name;
    public Person() {}
    public Person(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "Person{name='" + name + "'}";
    }
/*省略getter&setter*/
}

5.Stream流的结果收集

  • 一个流仅能收集一次结果
  • 收集结果分为三种方式:收集到List,收集到Set,收集到数组。

5.1 收集到集合

  • collect(Collector<T>);
    • 可通过Collectors工具类的方法获得Collector实例

5.1.1 收集到List集合

  • 使用格式:流名.collect(Collectors.toList());

5.1.2 收集到Set集合

  • 使用格式:流名.collect(Collectors.toSet());

5.2 收集到数组

  • 方法1:Object[] toArray();
    • 该方法返回值是Object[]

但返回值是个Object[],如何解决泛型数组的问题?

使用lambda和方法引用对toArray进行重载,传递一个IntFunction<A[]>的函数,从而从外面指定泛型参数。

  • 方法2:<A> A[] toArray(IntFunction<A[]> generator);
    • 该方法返回的数组将根据传入的IntFunction的类型确定。
    • toArray()的参数是IntFunction接口的实现类,可用lambda表达式,也可用方法引用
public class Notes05 {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("10", "20", "30", "40", "50");
        //方式1:
        Object[] objArray = stream.toArray();
        //方式2:
        String[] strArray = stream.toArray(String[]::new);//stream.toArray(length -> new String[length]);
    }
}

6. 并发流

上述例子均使用串行流(单线程执行),Stream也可以使用并行流(多线程执行)

  • default Stream stream():返回串行流;
  • default Stream parallelStream():返回并行流;

6.1 流的概述

  • 串行流:在当前线程中按序执行
  • 并发流: 开启多个线程,和当前线程一同执行(开启的线程个数由系统根据计算机性能自动匹配)

6.2 获取并发流

6.2.1 串行流转换为并发流

  • 通过串行流对象调用parallel方法获得:串行流名.parallel();

6.2.2 直接获取并发流

  • 通过集合对象调用parallelStream方法直接获得:集合对象名.parallelStream();

6.3 串行流和并发流的选择

  1. 数据量不大时,串行流往往更快。因为开线程池和配备资源也要时间;
  2. 若任务涉及I/O操作且任务间不相互依赖,可以考虑并行化。

    • 若任务之间是独立的,表明代码是可被并行化
    • 因为并行环境中任务的执行顺序是不确定的,因此对依赖顺序的任务而言,并行化也许不能给出正确的结果

猜你喜欢

转载自blog.csdn.net/KeepStruggling/article/details/81865247