文章目录

  • 简介
  • 操作流程
  • 流的创建
  • 中间操作
  • 最终操作
  • 简单案例
  • 扩展
  • parallelStream并行流
  • 创建方式
  • Optional
  • 声明式编程/命令式编程?


简介

Java 8 添加了一个新的抽象概念称为流Stream,可以让你以一种声明的方式处理数,据提供一种对 Java 集合运算和表达的高阶抽象。它与 java.io包里的 InputStream和 OutputStream是完全不同的概念。它将要处理的元素集合看作一种流, 流在管道中传输, 可以在管道的节点上进行数据处理, 比如筛选, 排序,聚合等。Stream 是对集合对象功能的增强,借助于lambda表达式,展现更优雅的表达风格,极大的提高编程效率和程序可读性。

Stream 特点

  1. 不存储数据,而是按照特定的规则对数据进行计算,一般会输出结果;
  2. 不会改变数据源,通常情况下会产生一个新的集合;(我们在流中可以多数据做很多处理。但是正常情况下是不会影响原来集合中的元素的。这往往也是我们期望的)
  3. 具有延迟执行特性,只有调用终端操作时,中间操作才会执行。
  4. 不可复用,对一个已经进行过终端操作的流再次调用,会抛出异常。

操作流程

流的创建

  1. java.util.Collection.stream() 方法用集合创建流(常用)
List<String> list = Arrays.asList("1", "2", "3", "4", "5", "6");
Stream<String> stream = list.stream();
  1. java.util.Arrays.stream(T[] array)方法用数组创建流
int[] array={1,3,5,6,8};
IntStream stream = Arrays.stream(array);
  1. 静态方法:of()、iterate()、generate()
Stream<Integer> stream1 = Stream.of(1, 2, 3, 4, 5, 6);
Stream<Integer> stream2 = Stream.iterate(0, (x) -> x + 7).limit(6);
Stream<Double> stream3 = Stream.generate(Math::random).limit(5);
  1. 通过文件生成
// 通过Files.line方法得到一个流,流的内容是按行读取的文件信息.
Stream<String> lines = Files.lines(Paths.get("data.txt"), Charset.defaultCharset());

中间操作

其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。

filter:从流中排除元素,filter的方法参数为一个条件(过滤保留函数返回值为 true 的元素)

List<String> list = Arrays.asList("apple", "banana", "cat", "dog");
// 过滤以"a"开头的字符串
List<String> filteredList = list.stream().filter(s -> s.startsWith("a")).collect(Collectors.toList());

distinct:去重(利用hashCode和equals方法来判断两个元素是否相等)

List<String> list = Arrays.asList("apple", "banana", "apple", "cat", "dog", "banana");
// 去除重复元素
List<String> distinctList = list.stream().distinct().collect(Collectors.toList());

limit:通过limit方法指定返回流的个数,limit的参数值必须 >=0,否则将会抛出异常。

List<String> list = Arrays.asList("apple", "banana", "cat", "dog");
// 只保留流中的前两个元素
List<String> limitedList = list.stream().limit(2).collect(Collectors.toList());

skip:跳过流中元素,参数值必须>=0

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
// 跳过前三个数据后遍历数据
Stream<Integer> stream = numbers.stream().skip(3).forEach(System.out::println);

map:接收一个函数作为参数,应用到每个元素上。(mapToDouble、mapToInt、mapToLong等)

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 将每个数转化为其平方值
List<Integer> mappedNumbers = numbers.stream().map(n -> n * n).collect(Collectors.toList());

flatMap:将一个流中的每个值都转换成另一个流,再将所有流连接成一个流。

List<List<String>> listOfLists = Arrays.asList(Arrays.asList("apple"), Arrays.asList("banana", "cherry", "date"));
// 将嵌套列表转换为平铺列表
List<String> flattenedList = listOfLists.stream().flatMap(Collection::stream).collect(Collectors.toList());

peek:可以在Stream的每个元素恰好被消费前,而不改变流中的元素内容

List<String> list = Arrays.asList("apple", "banana", "cat");
// 在字符串转换为大写之前和之后分别打印它们的值,但并不改变原始字符串列表
List<String> modifiedList = list.stream().peek(s -> System.out.println("before: " + s)).map(String::toUpperCase).peek(s -> System.out.println("after: " + s)).collect(Collectors.toList());

sorted:按自然排序,或者实现Comparator 接口,写比较器,自定义排序

List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5, 9, 2, 6, 5);
// 对数字列表进行排序
List<Integer> sortedNumbers = numbers.stream().sorted().collect(Collectors.toList());

中间操作是延迟执行的,直到执行终止操作才执行。

最终操作

allMatch(所有元素)、anyMatch(其中一个)、noneMatch(全不匹配):判断流中是否全部、任意一个、全不满足指定条件的元素

List<String> list = Arrays.asList("apple", "banana", "cat", "dog");
// 判断是否存在任意一个以"a"开头的字符串,这里为true
boolean anyMatch = list.stream().anyMatch(s -> s.startsWith("a"));

findFirst(查找第一个)、findAny(随机查找一个):返回流中的第一个或任意一个元素

List<String> list = Arrays.asList("apple", "banana", "cat", "dog");
// 返回列表中的第一个元素,这里为"apple"
Optional<String> first = list.stream().findFirst();

count:统计流中元素个数(max:最大值、min:返回流中最小值)

List<String> list = Arrays.asList("apple", "banana", "cat", "dog");
// 计算列表中元素的数量,这里为4
long count = list.stream().count();

forEach:对流中的每个元素执行指定操作

List<String> list = Arrays.asList("apple", "banana", "cat", "dog");
// 对列表中的每个字符串进行打印输出
list.stream().forEach(s -> System.out.println(s));

reduce:通过累加器将流中的所有元素组合成一个单一的值。能实现对集合求和、求乘积和求最值操作。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 将数字列表中的所有数字相加,得到总和
Optional<Integer> sum = numbers.stream().reduce((x, y) -> x + y);

collect:将流转换为指定类型的集合或数组等数据结构

List<String> list = Arrays.asList("apple", "banana", "cat", "dog");
// 将列表转换为一个Set对象
Set<String> set = list.stream().collect(Collectors.toSet());
// 将列表转换为一个map对象
Map<String, Integer> map = stream.collect(Collectors.toMap(item -> item.getKey(), item -> item.getValue()));
// 合并流中所有元素为一个字符串
String result = stream.collect(Collectors.joining(","));
// 统计流中元素的个数、总和、平均值、最大值和最小值:
IntSummaryStatistics stats = stream.collect(Collectors.summarizingInt(Integer::intValue));
// 对流中的元素根据条件进行分组
Map<String, List<Person>> groups = stream.collect(Collectors.groupingBy(Person::getGroup));
// 对流中的元素根据条件进行分区(key只能为true、false):
Map<Boolean, List<Person>> partitions = stream.collect(Collectors.partitioningBy(Person::isAdult));

ps:流只能使用一次,进行了终止操作后,该流就不能在使用了,会报错。

简单案例

假设有一个Student类,其中包含属性id、name、age、gender。现在有一个List对象students,需要对其中的元素进行以下操作:

  1. 将年龄大于等于18岁的女性过滤出来,并按照姓名从A到Z排序;
  2. 对结果中的每个元素调用一个Web API获取地址信息,然后将地址信息添加到Student对象的address属性中;
  3. 对结果按照地址排序,并将结果输出为字符串。
List<Student> students = new ArrayList<>();
students.add(new Student(1, "张三", 22, Gender.MALE));
students.add(new Student(2, "李四", 17, Gender.FEMALE));
students.add(new Student(3, "王五", 20, Gender.FEMALE));
students.add(new Student(4, "赵六", 18, Gender.MALE));

String result = students.stream()
                        .filter(s -> s.getAge() >= 18 && s.getGender() == Gender.FEMALE)
                        .sorted(Comparator.comparing(Student::getName))
                        .map(s -> {
                            String address = WebAPI.getAddress(s.getName());
                            s.setAddress(address);
                            return s;
                        })
                        .sorted((s1, s2) -> s1.getAddress().compareTo(s2.getAddress()))
                        .map(s -> s.toString())
                        .collect(Collectors.joining(","));
System.out.println(result);

假设有一个Person类,其中包含属性name、age、gender和job。现在有一个List对象persons,需要对其中的元素进行以下操作:

  1. 将所有Person对象按照性别分组;
  2. 对于每个性别,将其内部的Person对象按照照职业进行分组;
  3. 对于每个职业,统计该职业的人数。
List<Person> persons = new ArrayList<>();
persons.add(new Person("张三", 22, Gender.MALE, "程序员"));
persons.add(new Person("李四", 17, Gender.FEMALE, "销售"));
persons.add(new Person("王五", 20, Gender.FEMALE, "程序员"));
persons.add(new Person("赵六", 18, Gender.MALE, "销售"));

Map<Gender, Map<String, Long>> result = persons.stream()
                                               .collect(Collectors.groupingBy(Person::getGender, Collectors.groupingBy(Person::getJob, Collectors.counting())));
System.out.println(result);

扩展

parallelStream并行流

stream是顺序流,由主线程按顺序对流执行操作,而parallelStream是并行流,内部以多线程并行执行的方式对流进行操作,但前提是流中的数据处理没有顺序要求,同时要注意线程安全

创建方式
  1. 调用集合类的parallelStream()方法,将普通的串行流转换为并行流,例如:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
Stream<Integer> stream = numbers.parallelStream();
  1. 使用Stream接口中的静态方法parallel()创建并行流,例如:
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6).parallel();
  1. 使用Arrays类的parallelStream()方法创建并行流,例如:
int[] numbers = {1, 2, 3, 4, 5, 6};
IntStream stream = Arrays.parallelStream(numbers);

Optional

Optional类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。

声明式编程/命令式编程?

声明式编程与命令式编程是两种不同的编程范式。在命令式编程中,程序员需要指定计算机执行的具体步骤和控制流程,即通过一系列的命令来改变计算机的状态,从而实现要求的功能。而在声明式编程中,程序员则更关注于描述问题本身,而非实现解决问题的步骤。

简单来说,声明式编程将问题定义为一个表达式或者问题的规则集合,然后利用现有的函数、模块或库来求解这个问题。