Java 1.8 新特性——Stream 流的详细介绍

在 Java 1.8 中,得益于 Lambda 表达式所带来的函数式编程,引入了 Stream 流,用于解决已有集合类库的弊端

一、传统集合的遍历

几乎所有的集合(如Collection接口或Map接口等)都支持直接或间接的遍历操作。而当我们需要对集合中的元素进行操作的时候,除了必需的添加、删除、获取外,最典型的就是集合遍历,如:

package cn_lemon;

import java.util.ArrayList;
import java.util.List;

public class ForeachDemo {
    public static void main(String[] args) {
        List<String> l = new ArrayList<>();
        l.add("李彦宏");
        l.add("马化腾");
        l.add("刘强东");
        l.add("马云");
        l.add("张朝阳");
        l.add("马冬梅");
        l.add("马东敏");
        for (String s : l) {
            System.out.println(s);
        }
    }
}

1、循环遍历的弊端
Java 8的Lambda让我们可以更加专注于做什么(What),而不是怎么做(How),这点此前已经结合内部类进行了对比说明。现在,我们仔细体会一下上例代码,可以发现:

  • for循环的语法就是“怎么做
  • for循环的循环体才是“做什么

为什么使用循环?因为要进行遍历。但循环是遍历的唯一方式吗?遍历是指每一个元素逐一进行处理,而并不是从第一个到最后一个顺次处理的循环。前者是目的,后者是方式。

试想一下,如果希望对集合中的元素进行筛选过滤:

  • 1.将集合A根据条件一过滤为子集B
  • 2.然后再根据条件二过滤为子集C
package cn_lemon;

import java.util.ArrayList;
import java.util.List;

public class ForeachDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("李彦宏");
        list.add("马化腾");
        list.add("刘强东");
        list.add("马云");
        list.add("张朝阳");
        list.add("马冬梅");
        list.add("马东敏");

        List<String> malist = new ArrayList<>();
        for (String name1 : list) {
            if (name1.startsWith("马")) {
                malist.add(name1);
            }
        }

        List<String> threelist = new ArrayList<>();
        for (String name2 : malist) {
            if (name2.length() == 3) {
                threelist.add(name2);
            }
        }

        for (String name3 : threelist) {
            System.out.println(name3);//输出:马化腾 马冬梅 马东敏
        }
    }
}

在上面的代码中,有三个循环:

  • 1、筛选 List 集合中所有姓 “马” 的人
  • 2、筛选名字有三个字的人
  • 3、对结果进行打印输出

每当我们需要对集合中的元素进行操作的时候,总是需要进行循环、循环、再循环。循环是做事情的方式,而不是目的。另一方面,使用线性循环就意味着只能遍历一次。如果希望再次遍历,只能再使用另一个循环从头开始。
那,Lambda的衍生物Stream能给我们带来怎样更加优雅的写法呢?

使用 Stream 的写法

package cn_lemon;

import java.util.ArrayList;
import java.util.List;

public class ForeachDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("李彦宏");
        list.add("马化腾");
        list.add("刘强东");
        list.add("马云");
        list.add("张朝阳");
        list.add("马冬梅");
        list.add("马东敏");

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

在这段代码中,并没有体现使用线性循环或是其他任何算法进行遍历,直接:获取流、过滤姓马、过滤长度为3、逐一打印

Java 1.8 新特性——Stream 流的详细介绍_第1张图片

二、Stream 流式思想的概述

1、整体概述:流式思想类似于工厂车间的流水线
Java 1.8 新特性——Stream 流的详细介绍_第2张图片

当需要对多个元素进行操作(特别是多步操作)的时候,考虑到性能及便利性,我们应该首先拼好一个“模型”步骤方案,然后再按照方案去执行它。
Java 1.8 新特性——Stream 流的详细介绍_第3张图片
这张图展示了过滤、映射、跳过、计数等多步操作,每一个方框都是一个“流”,调用指定的方法,可以从一个流模型转换为另一个流模型。这里的 filter、map、skip都是在对函数模型进行操作,集合元素并没有真正被处理,只有当终结方法 count 执行的时候,整个模型才会按照指定策略执行操作。这得益于 Lambda 表达式延迟执行的特性

备注:”Stream 流“ 其实是一个集合元素的函数模型,它不是集合,也不是数据结构

Stream(流)是一个来自数据源的元素队列

  • 元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
  • 数据源 流的来源。 可以是集合,数组 等。

和 Collection 操作不同, Stream操作还有两个基础的特征:

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

当使用一个流的时候,通常包括三个基本步骤:获取一个数据源(source)→ 数据转换→执行操作获取想要的结果,每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道。

三、获取流

Java 1.8 新特性——Stream 流的详细介绍_第4张图片
java.util.stream.Stream是Java 8新加入的最常用的流接口(这并不是一个函数式接口)

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

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

1、根据 Collection 获取流

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

package cn_lemon;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;

class ConDemo {
    public static void main(String[] args) {
        List<String> list = null;
        list.stream();//List 的Stream 方法

        Set<String> set = null;
        set.stream();// Set 的 Stream 方法

        Map map = null;
        map.values().stream();//Map 集合中 value 的stream 方法
        map.keySet().stream();
        map.entrySet().stream();

        int[] array = null;//数组是需要用工具类获取流的
        Arrays.stream(array);//获取流
    }
}

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

package cn_lemon;

import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;

class MapDemo {
    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();
    }
}

3、数组获取流

由于数组对象不可能添加默认方法,所以Stream接口中提供了静态方法ofof方法的参数其实是一个可变参数,所以支持数组

package cn_lemon;

import java.util.Arrays;
import java.util.stream.Stream;

class ArrayDemo {
    public static void main(String[] args) {
        String[] array = {"马化腾", "马云", "张一鸣", "刘强东", "李彦宏", "张朝阳", "张志东"};
        Stream<String> stream = Stream.of(array);//数组获取流
        Arrays.stream(array).filter(s -> s.startsWith("马")).filter(s -> s.length() == 3).forEach(s -> System.out.println(s));
    }
}

四、常用方法

Java 1.8 新特性——Stream 流的详细介绍_第5张图片
流模型的操作很丰富,这里介绍一些常用的API。这些方法可以被分成两种:

  • 延迟方法:返回值类型仍然是Stream接口自身类型的方法,因此支持链式调用。(除了终结方法外,其余方法均为延迟方法。)
  • 终结方法:返回值类型不再是Stream接口自身类型的方法,因此不再支持类似StringBuilder那样的链式调用。本小节中,终结方法包括countforEach方法。

1-1、forEach 逐一处理

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

import java.util.stream.Stream;

class ForEachDemo {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("马化腾", "马云", "张一鸣", "刘强东", "李彦宏", "张朝阳", "张志东");
        stream.forEach(s -> System.out.println(s));
    }
}

1-2、Consumer 接口

  • Consumer 原意:消费者,就是消费一个数据,其数据类型由泛型决定
  • 抽象方法 accept
  • 默认方法 andThen
package cn_lemon;

import java.util.function.Consumer;

class ConsumerDemo {
    public static void consumerStream(Consumer<Integer> con) {
        con.accept(4317622);
    }

    public static void main(String[] args) {
        consumerStream(s -> System.out.println(s));
    }
}
package cn_lemon;

import java.util.function.Consumer;

class ConsumerDemo {
    public static void consumerStream(Consumer<String> one, Consumer<String> two) {
        one.andThen(two).accept("Hello Lemon");
    }

    public static void main(String[] args) {
        consumerStream(
                s -> System.out.println(s.toLowerCase()),
                s -> System.out.println(s.toUpperCase())
        );
    }
}
package cn_lemon;

import java.util.function.Consumer;

class ConsumerDemo {
    public static void man(Consumer<String> one, Consumer<String> two, String[] array) {
        for (String s : array) {
            one.andThen(two).accept(s);
        }
    }

    public static void main(String[] args) {
        String[] array = {"马化腾,腾讯", "马云,阿里巴巴", "李彦宏,百度"};
        man(
                s -> System.out.print("姓名:" + s.split(",")[0]),
                s -> System.out.println("---,来自:" + s.split(",")[1]), array
        );
    }
}

2-1、filter 过滤

  • filter 方法是将一个流转换成另一个子集流
  • Stream filter(Predicate predicate);
  • 该接口接收一个Predicate函数式接口参数(可以是一个Lambda或方法引用)作为筛选条件

Java 1.8 新特性——Stream 流的详细介绍_第6张图片

2-2、Predicate 接口

  • Predicate 原意 : 谓语 ,是对某种类型的数据进行判断,得到一个 boolean
  • 抽象方法 test
package cn_lemon;

import java.util.function.Predicate;

class PredicateDemo {
    private static void predi(Predicate<String> p) {
        boolean b = p.test("让世界充满爱");
        System.out.println("字符串的长度大于5吗??? ---" + b);
    }

    public static void main(String[] args) {
        predi(s -> s.length() > 5);
    }
}

3-1、Map 映射

  • 将流中的元素映射到另一个流中
  • Stream map(Function mapper);
  • 该接口需要一个Function函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流

Java 1.8 新特性——Stream 流的详细介绍_第7张图片

package cn_lemon;

import java.util.ArrayList;
import java.util.List;

public class MapDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("10");
        list.add("20");
        list.add("30");
        list.add("40");
        list.add("50");

        list.stream().map(s -> Integer.parseInt(s)).forEach(s -> System.out.println(s));
    }
}

3-2、Function 接口

  • Function 接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件
  • 抽象方法 apply
  • 默认方法 andThen
package cn_lemon;

import java.util.function.Function;

class FunctionDemo {
    private static void method(Function<String, Integer> fun) {//将 String 类型转换成 Integer 类型
        int num = fun.apply("10");//抽象方法 apply
        System.out.println(num + 20);
    }

    public static void main(String[] args) {
        method(s -> Integer.parseInt(s));
    }
}
package cn_lemon;

import java.util.function.Function;

class FuncDemo {
    private static void method(Function<String, Integer> one, Function<Integer, Integer> two) {
        int num = one.andThen(two).apply("10");
        System.out.println(num + 20);//结果输出 220
    }

    public static void main(String[] args) {
        method(str -> Integer.parseInt(str) + 30, i -> i = i * 5);
    }
}

4、count 统计个数

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

import java.util.Arrays;
import java.util.stream.Stream;

public class CountDemo {
    public static void main(String[] args) {
        String[] array = {"马化腾", "马云", "张一鸣", "张志东", "李彦宏", "马东敏"};
        Stream<String> result = Arrays.stream(array).filter(s -> s.startsWith("马"));
        System.out.println(result.count());
    }
}

或者

package cn_lemon;

import java.util.stream.Stream;

public class CountDemo {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("马化腾", "马云", "张一鸣", "张志东", "李彦宏", "马东敏");
        Stream<String> result = stream.filter(s -> s.startsWith("马"));
        System.out.println(result.count());
    }
}

5、limit 取用前几个

  • limit`方法可以对流进行截取,只取用前n个
  • 参数是一个long型,如果集合当前长度大于参数则进行截取;否则不进行操作

Java 1.8 新特性——Stream 流的详细介绍_第8张图片

package cn_lemon;

import java.util.stream.Stream;

public class CountDemo {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("马化腾", "马云", "张一鸣", "张志东", "李彦宏", "马东敏");
        Stream<String> result = stream.limit(2);
        System.out.println(result.count());//输出 2
    }
}

6、skip 跳过前几个

  • skip方法获取一个截取之后的新流

Java 1.8 新特性——Stream 流的详细介绍_第9张图片

package cn_lemon;

import java.util.stream.Stream;

public class CountDemo {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("马化腾", "马云", "张一鸣", "张志东", "李彦宏", "马东敏");
        Stream<String> result = stream.skip(3);
        System.out.println(result.count());//输出 3
    }
}

7、concat 组合

  • 如果有两个流,希望合并成为一个流,那么可以使用Stream接口的静态方法concat
  • 这是一个静态方法,与java.lang.String当中的concat方法是不同的
package cn_lemon;

import java.util.stream.Stream;

public class CountDemo {
    public static void main(String[] args) {
        Stream<String> streamA = Stream.of("张志东", "李彦宏", "马东敏");
        Stream<String> streamB = Stream.of("马化腾", "马云", "张一鸣");
        Stream<String> result = Stream.concat(streamA, streamB);//把两个流合并成一个流
    }
}
package cn_lemon;

import java.util.stream.Stream;

public class CountDemo {
    public static void main(String[] args) {
        Stream<String> streamA = Stream.of("张志东", "李彦宏", "马东敏");
        Stream<String> streamB = Stream.of("马化腾", "马云", "张一鸣");
        Stream.concat(streamA, streamB).forEach(s -> System.out.println(s));
    }
}

五、案例

有两个ArrayList集合存储队伍当中的多个成员姓名,依次进行以下若干操作步骤:

  1. 第一个队伍只要名字为3个字的成员姓名;存储到一个新集合中。
  2. 第一个队伍筛选之后只要前3个人;存储到一个新集合中。
  3. 第二个队伍只要姓张的成员姓名;存储到一个新集合中。
  4. 第二个队伍筛选之后不要前2个人;存储到一个新集合中。
  5. 将两个队伍合并为一个队伍;存储到一个新集合中。
  6. 根据姓名创建Person对象;存储到一个新集合中。
  7. 打印整个队伍的Person对象信息。

方法一:(传统集合遍历)

封装一个 Person.java

package cn_lemon;

public class Person {
    private String name;

    public Person() {

    }

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

    @Override
    public String toString() {
        return "人员名单:{姓名---“" + name + "”}";
    }

    public String getName() {
        return name;
    }

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

创建 NameDemo.java

package cn_lemon;

import java.util.ArrayList;
import java.util.List;

public class NameDemo {
    public static void main(String[] args) {
        List<String> listA = new ArrayList<>();
        listA.add("马云");
        listA.add("马化腾");
        listA.add("张一鸣");
        listA.add("王健林");
        listA.add("程维");
        listA.add("比尔盖茨");
        listA.add("马赛克");

        List<String> listB = new ArrayList<>();
        listB.add("张英");
        listB.add("马东敏");
        listB.add("李彦宏");
        listB.add("刘强东");
        listB.add("雷军");
        listB.add("李开复");
        listB.add("张强");
        listB.add("张朝阳");

        List<String> threelist = new ArrayList<>();
        for (String three_name : listA) {//筛选 A 集合中名字为 3 个字的人,存储到集合中
            if (three_name.length() == 3) {
                threelist.add(three_name);
            }
        }

        List<String> limitlist = new ArrayList<>();
        for (int i = 0; i < 3; i++) {//在筛选A之后的集合中,选前三个人
            limitlist.add(threelist.get(i));
        }

        List<String> zhanglist = new ArrayList<>();
        for (String zhang_name : listB) {//筛选集合 B 中姓张的
            if (zhang_name.startsWith("张")) {
                zhanglist.add(zhang_name);
            }
        }

        List<String> twolist = new ArrayList<>();
        for (int i = 2; i < zhanglist.size(); i++) {//在筛选B之后的集合中,不要前两个
            twolist.add(zhanglist.get(i));
        }

        List<String> totalnames = new ArrayList<>();//将筛选之后的集合合并
        totalnames.addAll(limitlist);
        totalnames.addAll(twolist);

        List<Person> totalPersonname = new ArrayList<>();//根据姓名创建Person 对象
        for (String name : totalnames) {
            totalPersonname.add(new Person(name));
        }

        for (Person per : totalPersonname) {//打印Person对象的信息
            System.out.println(per);
        }
    }
}

运行显示:
Java 1.8 新特性——Stream 流的详细介绍_第10张图片

方法二:(使用 Stream 流的方式)

封装一个 Person.java

package cn_lemon;

public class Person {
    private String name;

    public Person() {

    }

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

    @Override
    public String toString() {
        return "人员名单:{姓名---“" + name + "”}";
    }

    public String getName() {
        return name;
    }

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

创建 NameDemo.java

package cn_lemon;

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

public class NameDemo {
    public static void main(String[] args) {
        List<String> listA = new ArrayList<>();
        listA.add("马云");
        listA.add("马化腾");
        listA.add("张一鸣");
        listA.add("王健林");
        listA.add("程维");
        listA.add("比尔盖茨");
        listA.add("马赛克");

        List<String> listB = new ArrayList<>();
        listB.add("张英");
        listB.add("马东敏");
        listB.add("李彦宏");
        listB.add("刘强东");
        listB.add("雷军");
        listB.add("李开复");
        listB.add("张强");
        listB.add("张朝阳");

        Stream<String> streamA = listA.stream().filter(s -> s.length() == 3).limit(3);//A集合中,三个字的姓名,取前三个
        Stream<String> streamB = listB.stream().filter(s -> s.startsWith("张")).skip(2);//B集合中,姓张的,不要前两个
        Stream<String> totalstream = Stream.concat(streamA, streamB);//合并流
        totalstream.map(s -> new Person(s)).forEach(p -> System.out.println(p));
    }
}

或者:

在这里插入图片描述

你可能感兴趣的:(Java)