Java8中的Stream流相关用法学习

目录

一、Stream是什么

二、创建Stream

三、中间操作

3.1 filter()

3.2 map()

3.3 flatMap()

3.4 distinct()

3.5 limit()

四、终端操作

4.1 findAny(), 和 orElse()

4.2 sorted()

4.3 forEach()

4.4 count()

4.5 collect()

4.6 groupingBy()

4.7 average()

4.8 anyMatch()


一、Stream是什么

Java 8引入了一种新的编程模型,称为"流"(Stream)API,用于处理集合数据。Stream API支持声明式风格的编程,专注于数据的处理,而不是控制流。它可以用于读取、过滤、转换和聚合数据,而无需显式地操作集合对象

Stream API的主要特点包括:

  1. 管道模式:每个Stream操作都会产生一个新的Stream,形成一条流水线。这条流水线可以由多个中间操作(intermediate operations)和一个终端操作(terminal operation)组成。中间操作不会立即执行,而是延迟到终端操作时才执行,这样可以优化性能并支持惰性求值。

  2. 内存效率:Stream API设计的目标之一是减少内存占用。例如,在处理大型数据集时,Stream可以在不需要存储整个集合于内存的情况下工作。

  3. 并行处理:Stream API支持并行流,可以利用多核处理器的优势进行并行计算,提高程序性能。

  4. 函数式编程:Stream API与函数式编程的概念紧密相关,如filter(), map(), reduce()等方法,它们接收Lambda表达式作为参数,使得代码更简洁、易读。

以下是一些Stream API的例子:

// 创建一个Stream
List<String> list = Arrays.asList("one", "two", "three");
Stream<String> stream = list.stream();

// 中间操作:筛选和转换
stream.filter(s -> s.startsWith("t"))
    .map(String::toUpperCase)
    .forEach(System.out::println);  // 输出: TWO, THREE

// 终端操作:收集到新的列表
List<String> result = stream.filter(s -> s.length() > 3)
                            .collect(Collectors.toList());

在这个例子中,我们首先创建了一个Stream,然后通过filter筛选出以"t"开头的字符串,再通过map将其转换为大写,最后通过forEach打印结果。第二个例子中,我们使用collect终端操作将满足条件的元素收集到一个新的列表中。

二、创建Stream

例如,List.stream() 或 Arrays.stream(ints)

下面是创建stream流的几种方式:

        /**
         * 1. 创建一个stream流
         */
        //1.1 从集合创建流
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        System.out.println("集合numbers = " + numbers);
        Stream<Integer> stream = numbers.stream();
        System.out.println("stream = " + stream);

        //1.2 从数组创建流
        Integer[] arr = {1, 2, 3, 4, 5};
        System.out.println("数组arr = " + arr);
        Stream<Integer> stream1 = Arrays.stream(arr);
        System.out.println("stream1 = " + stream1);

        //1.3 使用Stream.of()创建Stream
        Stream<String> stream2 = Stream.of("a", "b", "c");
        System.out.println("使用Stream.of()创建stream2 = " + stream2);

运行程序可以看到下面结果:

java.util.stream.ReferencePipeline$Head@579bb367 是一种Java对象的默认字符串表示形式,通常称为"对象的哈希值表示"。这种表示方式在打印未重写toString()方法的对象时出现,主要包括以下部分:

  1. 类名:ReferencePipeline$Head 这代表的是对象所属的类。在这个例子中,它来自于java.util.stream包,表示一个流管道的头节点。

  2. @ 符号:这个符号标志着后面的部分是对象的内存地址的十六进制表示。在某些JVM实现中,这不是真正的内存地址,而是对象的哈希码,用于标识对象的唯一性。

  3. 十六进制数字:579bb367 是上述提到的哈希码或内存地址的十六进制表示。每个Java对象都有唯一的哈希码,除非有特殊情况导致哈希冲突。

总的来说,当你打印一个没有重写toString()方法的对象时,你会看到类似这样的输出,它提供了对象的类信息以及一个用于区分不同对象的哈希码。如果你想要更友好的输出,你应该覆盖toString()方法,自定义你需要显示的信息。例如,在自定义类中,你可以将对象的关键属性组合成一个字符串返回。

这里的‘ReferencePipelineHead`是Stream管道的一个内部类,代表了未执行任何操作的原始流。由于这是一个中间对象,所以直接打印它不会有具体的结果,只有当你执行一个终端操作时,才会得到实际的数据或行为。

三、中间操作

Intermediate operations: 如 filter()map()distinct() 等,这些操作不会立即执行,而是构建一个新的流,等待最终操作触发。

3.1 filter()

filter()方法用于根据给定的条件筛选流中的元素。以下是一个简单的代码示例:

import java.util.Arrays;
import java.util.List;

public class StreamFilterExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);

        // 使用filter()筛选出偶数
        List<Integer> evenNumbers = numbers.stream()
                .filter(n -> n % 2 == 0)
                .collect(Collectors.toList());

        System.out.println("Even Numbers: " + evenNumbers); // 输出:[2, 4, 6, 8]
    }
}

在这个例子中,我们创建了一个整数列表,然后使用stream()方法将其转换为Stream。接着,我们调用filter()方法,传入一个lambda表达式(n -> n % 2 == 0)作为参数,这意味着我们只保留能被2整除的数字。最后,我们使用collect()方法将筛选后的流收集到一个新的列表中。

当你运行这段代码,你会看到输出 [2, 4, 6, 8],这就是原始列表中所有的偶数。

请注意,filter()和其他中间操作一样,不会立即执行。只有在遇到终端操作,如collect()forEach()count()等时,才会开始执行流的所有操作。这种延迟执行的概念被称为"惰性求值"。

3.2 map()

用于将流中的每个元素应用一个函数得到一个新的结果,然后创建一个新的流,这个新流由应用函数后产生的结果组成。以下是一个简单的例子:

import java.util.Arrays;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");

        // 使用map()将字符串转换为长度
        List<Integer> lengths = names.stream()
                .map(Main::stringLength)
                .collect(Collectors.toList());

        lengths.forEach(System.out::println);  // 输出:5, 3, 7, 5
    }

    public static int stringLength(String s) {
        return s.length();
    }
}

在这个例子中,我们有一个包含名字的列表,我们使用map()方法将每个名字转换为其长度,最终得到了一个新的整数列表。

3.3 flatMap()

它用于处理流中的每个元素,并将其转换为另一个流,然后将所有这些流连接成一个新的单一流。这对于处理嵌套结构(例如,一个列表的每个元素都是另一个列表)特别有用。

以下是一个简单的flatMap()用法的例子,假设我们有一个用户类,每个用户有一个列表的地址:

public class User {
    private String name;
    private List<Address> addresses;

    // getters and setters...
}

public class Address {
    private String city;

    // getters and setters...
}

现在,我们有一个用户列表,并想获取所有城市的列表:

List<User> users = ...; // 初始化用户列表
List<String> allCities = users.stream()
    .flatMap(user -> user.getAddresses().stream())
    .map(Address::getCity)
    .collect(Collectors.toList());

在这个例子中,flatMap()将每个用户的地址列表展开成单独的流,然后map()方法将每个地址的city字段提取出来,最后通过collect()方法收集所有的城市到一个新的列表中。

这个例子展示了如何使用flatMap()处理嵌套的数据结构并将其展平为单一层次的结果。

3.4 distinct()

用于去除流中的重复元素。这个方法不会立即执行,而是在流的终端操作时(例如 collectforEach 等)才执行。以下是一个简单的代码示例:

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

public class DistinctExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 2, 4, 3, 5);

        // 使用 distinct() 去除重复元素
        Stream<Integer> distinctNumbers = numbers.stream().distinct();

        // 输出去重后的结果
        distinctNumbers.forEach(System.out::println);
    }
}

在这个例子中,我们首先创建了一个包含重复元素的列表 numbers。然后,我们调用 stream() 方法将其转换为 Stream,接着调用 distinct() 进行去重操作。最后,使用 forEach 终端操作打印出去重后的数字。

输出将是:

1
2
3
4
5

这里没有重复的元素,因为 distinct() 操作已经将他们去除。需要注意的是,distinct() 是根据对象的 equals() 方法来判断是否重复的,所以如果你自定义了类并覆写了 equals() 方法,那么这里的去重规则也会相应改变。

3.5 limit()

用于限制流中元素的数量。它会返回一个新的流,其中只包含原始流的前n个元素。如果原始流的元素数量少于n,那么新流将包含所有元素。这个操作是非阻塞的,也就是说,直到终端操作执行时,实际的数据提取才会发生。

以下是一个简单的代码示例:

import java.util.Arrays;
import java.util.List;

public class LimitExample {
    public static void main(String[] args) {
        List<String> numbers = Arrays.asList("One", "Two", "Three", "Four", "Five");

        // 使用 limit() 方法限制输出到前三项
        numbers.stream()
                .limit(3)
                .forEach(System.out::println);
    }
}

在这个例子中,numbers.stream().limit(3) 创建了一个新的流,该流只包含原列表的前三个元素。然后,forEach(System.out::println) 遍历并打印这些元素,所以输出将是:

One
Two
Three

请注意,limit() 操作不会改变原始集合的内容。它只是创建了一个新的、有限的视图来处理数据。

四、终端操作

Terminal operations: 如 collect()forEach()count(),这些操作会触发前面所有的中间操作,并返回结果或完成流。

4.1 findAny(), 和 orElse()

  • findAny(): 这是一个终端操作,用于找到流中的任意一个元素。对于无限流,它可能会立即返回,而对于有限流,它会在遍历完流后返回。如果流为空,它会返回一个Optional对象的空实例。例如:
Optional<Integer> firstEven = numbers.stream()
                                   .filter(n -> n % 2 == 0)
                                   .findAny();
if (firstEven.isPresent()) {
    System.out.println("First even number is: " + firstEven.get());
}
  • orElse(): 也是一个与Optional相关的终端操作。当Optional对象有值时,它会返回该值;如果没有值(即isPresent()返回false),则会返回传入orElse()方法的参数。例如:
Optional<String> optionalName = Optional.empty();
String name = optionalName.orElse("Default Name");
System.out.println(name); // 输出: Default Name
  • 结合filter()操作,下面是个完整的例子:
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");

Optional<String> startsWithA = names.stream()
                                    .filter(name -> name.startsWith("A"))
                                    .findAny();

String firstNameStartingWithA = startsWithA.orElse("No one starts with 'A'");
System.out.println(firstNameStartingWithA); // 输出: Alice 或者 No one starts with 'A'

在这个例子中,我们首先创建了一个名字列表,然后通过filter找出以"A"开头的名字,如果存在这样的名字,就用findAny找到第一个,最后使用orElse确保即使没有找到名字,也会返回一个默认值。

4.2 sorted()

用于对流中的元素进行排序。这个操作会返回一个新的流,其元素按照自然排序或者是提供的Comparator进行排序。如果原始流是并行流,那么结果流也是并行流。

以下是一个简单的代码示例:

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class Main {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(5, 2, 9, 1, 5, 6);

        // 使用sorted()对列表进行排序
        List<Integer> sortedNumbers = numbers.stream()
                .sorted()
                .collect(Collectors.toList());

        System.out.println(sortedNumbers); // 输出:[1, 2, 5, 5, 6, 9]
    }
}

在这个例子中,我们首先创建了一个未排序的整数列表。然后,我们调用stream()方法将其转换为流,接着使用sorted()进行排序,最后通过collect()收集到一个新的已排序的列表。

如果你想要自定义排序规则,你可以传递一个Comparatorsorted()方法,如下所示:

List<String> words = Arrays.asList("zebra", "apple", "cat", "banana");

words.stream()
    .sorted(Comparator.comparingInt(String::length))
    .forEach(System.out::println);

这段代码会按字符串长度进行排序,输出结果可能是:"cat""apple""banana""zebra"

4.3 forEach()

用于执行对每个流元素的操作。这个操作不会返回任何结果,而是纯粹为了副作用而存在,例如打印元素、更新UI等。

以下是一个简单的代码示例,展示如何使用forEach()遍历并打印一个整数列表:

import java.util.Arrays;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

        // 使用forEach打印列表中的每个元素
        numbers.forEach(System.out::println);
    }
}

在这个例子中,System.out::println是一个方法引用,相当于传递了一个lambda表达式 (Integer num) -> System.out.println(num) ,即对于列表中的每个元素,执行System.out.println()来打印它。

注意,forEach()操作一旦执行,流就会被消费掉,不能再被重复使用。此外,由于它是终端操作,所以在其后不能接着放置其他中间操作。

4.4 count()

用于计算流中元素的数量。这个操作是累积性的,所以它可以有效地处理大量数据,即使是在不可见的并行流中。count() 返回的是一个 long 类型的值,表示流中元素的数量。

以下是一个简单的代码示例,展示了如何使用 count() 方法统计数组中偶数的数量:

import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        int[] numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

        long evenCount = Arrays.stream(numbers)
                               .filter(n -> n % 2 == 0)
                               .count();

        System.out.println("Number of even numbers: " + evenCount);
    }
}

在这个例子中,我们首先创建了一个整数数组 numbers。然后,我们通过 Arrays.stream(numbers) 创建了一个 IntStream。接着,使用 filter(n -> n % 2 == 0) 过滤出所有的偶数。最后,count() 方法计算过滤后剩余的偶数个数,并将其存储在变量 evenCount 中。程序输出结果将是偶数的数量。

4.5 collect()

用于将流转换为其他形式,例如收集到 Collection、构建 Map 或者执行其他聚合操作。它可以与 Collectors 类提供的工厂方法一起使用,以实现各种常见的收集行为。

以下是一个简单的例子,展示了如何使用 collect() 方法将一个整数流收集到 ArrayList 中:

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class Main {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

        // 创建一个新的列表,只包含偶数
        List<Integer> evenNumbers = numbers.stream()
                .filter(n -> n % 2 == 0)
                .collect(Collectors.toList());

        System.out.println(evenNumbers); // 输出: [2, 4]
    }
}

在这个例子中,我们首先创建了一个数字列表,然后将其转换为流。接着,我们使用 filter() 过滤出偶数,最后调用 collect() 并传入 Collectors.toList() 来收集结果到一个新的 ArrayList 中。

collect() 可以配合不同的收集器完成更复杂的任务,例如分组、规约等。例如,如果你想统计列表中每个元素出现的次数,你可以这样使用:

Map<Integer, Long> countMap = numbers.stream()
        .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));

System.out.println(countMap); // 输出: {1=1, 2=1, 3=1, 4=1, 5=1}

在这个例子中,groupingBy() 聚合操作用于按元素值分组,counting() 则计算每组的数量。结果是一个 Map,其中键是原始列表中的元素,值是它们的计数。

4.6 groupingBy()

groupingBy()是Java 8 Stream API中的一个收集器,用于根据提供的函数将流中的元素分组到一个Map中。这个Map的键是由分组函数计算出来的,值则是一个列表,包含了具有相同键的所有元素。

以下是一个简单的例子,假设我们有一个Person对象的列表,每个Person有名字(name)和年龄(age),我们想要按照年龄将这些人分组:

import java.util.*;
import java.util.stream.*;

class Person {
    String name;
    int age;

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

    // getters for name and age
}

List<Person> people = Arrays.asList(
    new Person("Alice", 25),
    new Person("Bob", 30),
    new Person("Charlie", 25),
    new Person("Dave", 30)
);

Map<Integer, List<Person>> groupedPeople = 
    people.stream()
          .collect(Collectors.groupingBy(Person::getAge));

System.out.println(groupedPeople);

在这个例子中,groupingBy(Person::getAge)创建了一个Map,其中键是年龄,值是对应年龄的人的列表。运行这段代码后,输出将会是一个Map,显示每个年龄段的人都有哪些:

{25=[Person@3e2c7a8f, Person@6d8761b3], 30=[Person@7059b6df, Person@3305f532]}

这里的键是年龄(25和30),值是包含相应年龄的Person对象的列表。注意,由于Person类没有重写equals和hashCode,所以Person对象的默认表示是内存地址,通常看起来像"Person@somehexnumber"。如果需要按照名字或其他属性比较Person对象,应该在Person类中实现这些方法。

4.7 average()

用于计算流中所有元素的平均值。这个操作要求流中的元素必须是可以转换为 Double 类型的。如果流为空,average() 将返回 OptionalDouble.empty();否则,它将返回一个表示平均值的 OptionalDouble 对象。

下面是一个简单的代码示例,展示了如何使用 average() 来计算一组数字的平均值:

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

public class AverageExample {
    public static void main(String[] args) {
        double[] numbers = {1.5, 2.7, 3.3, 4.1, 5.0};

        OptionalDouble average = Arrays.stream(numbers)
                .average();

        if (average.isPresent()) {
            System.out.println("Average: " + average.getAsDouble());
        } else {
            System.out.println("No elements to calculate average.");
        }
    }
}

在这个例子中,我们首先创建了一个 double 数组 numbers,然后将其转换为一个流(Arrays.stream(numbers))。接着调用 average() 方法计算平均值。由于流中有元素,average() 返回了一个非空的 OptionalDouble,我们可以使用 getAsDouble() 获取实际的平均值并打印出来。如果流为空,average() 返回的将是空的 OptionalDouble,可以通过 isPresent() 判断是否为空。

4.8 anyMatch()

用于检查流中的任何一个元素是否满足给定的 predicate(断言)。如果找到至少一个匹配的元素,它就返回 true,否则返回 false。这是一个短路操作,一旦找到匹配项,它就会立即停止处理剩余的元素。

以下是一个简单的代码示例,我们有一个整数列表,我们想检查是否存在任意一个偶数:

import java.util.Arrays;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);

        boolean hasEvenNumber = numbers.stream()
                .anyMatch(number -> number % 2 == 0);

        System.out.println("Does the list contain any even number? " + hasEvenNumber);
    }
}

在这个例子中,anyMatch(number -> number % 2 == 0) 使用了一个 lambda 表达式作为 predicate,检查每个数字是否能被2整除。因为列表中有偶数(2、4、6),所以 anyMatch() 返回 true

参考

【Java】Java8 之Stream用法总结(持续更新)_java 8 stream-CSDN博客

  • 23
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值