Java中的Stream流库

本文深入讲解Java中的Stream API,包括Stream的概念、特点及其操作步骤。详细介绍如何创建Stream、Stream的各种转换方法,以及如何使用Stream的终结操作。同时,还探讨了Optional类型的应用场景和最佳实践。

1. 什么是流

1.1 概念

Stream不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的Iterator。原始版本的 Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作;高级版本的Stream,用户只要给出需要对其包含的元素执行什么操作,比如 “过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母”等,Stream会隐式地在内部进行遍历,做出相应的数据转换。

1.2 特点

  • 流并不存储其元素
  • 流的操作不会修改其数据源
  • 流的操作是尽可能惰性执行的,就是说到了非要有结果的时候,它才会执行

2. 流的操作步骤

如Demo所示,这个工作流是操作流时的典型流程,其统计了“alice.txt”文件中单词字母长度大于10的单词数量。我们建立了一个包含三个阶段的操作管道:

  1. 创建一个流
  2. 指定初始流转换为其他流的中间操作,可能包含多个步骤
  3. 应用终止操作,从而产生结果。这个操作会强制执行之前的惰性操作,从此之后这个流就再也不能用了。
package example1;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;

public class Demo {
    public static void main(String[] args) throws IOException {
        String contents = new String(Files.readAllBytes(
                Paths.get("alice.txt")), StandardCharsets.UTF_8);
        List<String> words = Arrays.asList(contents.split("\\PL+")); //以非字母为分隔符
        long count = words.parallelStream()     //创建流
                .filter(w -> w.length() > 10)   //转换操作(过滤长度大于10的字符)
                .count();                       //终止操作(统计数量)
        System.out.println(count);
    }
}

3. 流的创建

Collection接口的stream方法可以将任何集合转换成流。如果有一个数组就使用静态的Stream.of方法。

//将集合转换成流
List<String> words = ...;
Stream<String> s1 = words.stream();
//将数组转换成流
String[] array = ...;
Stream<String> s2 = Stream.of(array);
//of方法还可以选择截取数组来创建流  of(array, from, to):包含from,不包含to
Stream<String> s3 = Stream.of(array, 0, 5);
//创建一个不包含任何元素的流
Stream<String> s4 = Stream.empty();

创建无限流有两种方法:

  1. 调用Stream.generate()方法
  2. 调用Stream.iterate()方法
package example2;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Create {
    public static void main(String[] args) throws IOException {
        //对于String类型的数组,用Stream.of方法来创建流
        Stream<String> words = Stream.of(new String(
                Files.readAllBytes(Paths.get("alice.txt")), StandardCharsets.UTF_8)
                .split("\\PL+"));
        show("words", words);

        Stream<String> song = Stream.of("gently", "down", "the", "stream");
        show("song", song);

        //创建一个不包含任何元素的流
        Stream<String> empty = Stream.empty();
        show("empty", empty);

        //创建一个String类型的无限流
        Stream<String> echos = Stream.generate(() -> "Echo");
        show("echos", echos);

        //创建一个产生随机数的无限流
        Stream<Double> randoms = Stream.generate(Math::random);
        show("randoms", randoms);

        //创建一个无限序列,开始为0,每次加1
        //iterate方法可以给出一个种子值,以及一个函数
        Stream<Integer> integers = Stream.iterate(0, n -> n + 1);
        show("integers", integers);

        //Pattern类有一个splitAsStream方法来按照给出的正则表达式分割一个CharSequence对象
        Stream<String> wordsAnotherWay = Pattern.compile("\\PL+")
                .splitAsStream(new String(
                        Files.readAllBytes(Paths.get("alice.txt")), StandardCharsets.UTF_8));
        show("wordsAnotherWay", wordsAnotherWay);

        //按照alice.txt的行来创建一个流
        Stream<String> lines = Files.lines(Paths.get("alice.txt"));
        show("lines", lines);
    }

    public static <T> void show(String title, Stream<T> stream) {
        final int SIZE = 10;
        List<T> firstElements = stream.limit(10).collect(Collectors.toList());
        System.out.print(title + ": ");
        for (int i = 0; i < firstElements.size(); i++) {
            if (i > 0) {
                System.out.print(",");
            }
            if (i < SIZE) {
                System.out.print(firstElements.get(i));
            } else {
                System.out.print("...");
            }
        }
        System.out.println();
    }
}

3. 流的转换

流的转换会产生一个新的流,它的元素派生自另外一个流中的元素。

3.1 filter、map、flatMap方法

//返回由与此给定谓词匹配的此流的元素组成的流
Stream<T> filter(Predicate<? super T> predicate)
//返回由给定函数应用于此流的元素的结果组成的流
<R> Stream<R> map(Function<? super T,? extends R> mapper)
//返回由通过将提供的映射函数应用于每个元素而产生的映射流的内容来替换该流的每个元素的结果的流
<R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)

filter方法转换一个流,它的元素与给定的条件相匹配

map方法转换一个流,它的作用是按照给定的函数来转换流中的值

flatMap方法转换一个流,它的作用是将当前流中所有元素产生的结果连接到一起

3.2 抽取子流和连接流

抽取子流有两个方法:limit()和skip()

//limit方法获取前n个元素 
//例如获取前100个元素的流
Stream<Double> randoms = Stream.generate(Math::random).limit(100);

//skip方法是丢弃前n个元素,保留n之后的
//获取100以后的元素
Stream<Integer> integers = Stream.iterate(0, x -> x + 1).skip(100);

连接流方法:concat()

//contact方法连接两个流
//假设letters("Hello")的结果是["H","e","l","l","o"]的流
Stream<String> combined = Stream.concat(letters("Hello"), letters("World"));

注意:连接两个流时,前一个流不能是一个无限流

4. 流的终结

4.1 min和max

//使用min方法终结流,得到流的最小值
Optional<String> smallest = words.min(String::compareToIgnoreCase);
System.out.println("smallest = " + smallest);
 
//使用max方法终结流,得到流的最大值
Optional<String> largest = words.max(String::compareToIgnoreCase);
System.out.println("largest = " + largest);

4.2 findFirst和findAny

//findFirst方法返回与之匹配的非空集合的第一个值
//注意,如果没有任何值与之匹配就返回一个空的Optional值
Optional<String> first = words.filter(s -> s.startsWith("Q")).findFirst();
 
//findAny方法返回任意匹配的任意一个值
//注意,如果没有任何值与之匹配就返回一个空的Optional值
Optional<String> any = words.filter(s -> s.startsWith("Q")).findAny();

4.3 anyMatch, allMatch和noneMatch

//如果想知道有任意元素与给出条件匹配用anymatch,返回的是boolean值
boolean anymatch = words.parallel().anymatch(s->s.contains("e"));
 
//查看是否所有元素都与给出条件匹配用allmatch,返回值是boolean值
boolean allmatch = words.parallel().allmatch(s->s.contains("e"));
 
//查看是否没有任何元素与给出条件匹配,返回值是boolean值
boolean nonematch = words.parallel().nonematch(s->s.contains("e"));

5. Optional类型

Optional对象是一种包装器对象,要么包装了T类型对象,要么没有包装任何对象。Optional类型被当作一种更安全的方式,用来代替类型T的引用,这种引用要么引用某个对象,要么为null。

5.1 如何使用Optional值

Optional在值存在的时候,才会使用这个值;在值不存在时,它会有一个可替代物。我们先看看Optional类的三个简单方法:

//orElse()方法,如果存在值,就用原值,如果不存在值就使用一个值来替代它
//我们这里用空字符串来替代没有值的情况
String result = optionalString.orElse("");

//orElseGet()方法,后面可以接一个lambda表达式,表示如果不存在值的话就用表达式的值来替代它
String result = optionalString.orElseGet(() -> Locale.getDefault().getDisplayName());

//orElseThrow()方法,如果不存在值,就在方法中抛出对应的异常
String result = optionalString.orElseThrow(IllegalStateException::new);

还有一个ifPresent()方法,ifPresent(v->Process v)该方法会接收一个函数,如果在值存在的情况下,它会将值传递给该函数,但是如果在值不存在的情况下,它不会做任何事。

//比如我们想要在值存在的情况下,就把值添加到一个集中
optionalValue.ifPresent(v->result.add(v));
//或者
optionalValue.ifPresent(result::add);

5.2 不适合使用Optional值的方式

不适合使用Optional值的情况有两种:

需要用到get()方法,因为Optional值在不存在的情况下,使用get方法会抛出NoSuchElementException异常。

Optional<T> optionalValue = ...;
optionalValue.get().somemethod();
//这种方式并不比下面安全
T value = ...;
value.somemethod();

需要用到isPresent()方法作非空判断时。

if(optionalValue.isPresent()){
    optionalValue.get().somemethod();
}
//这种方式也并不比下面容易处理
if(value != null){
    value.somemethod();
}

5.3 创建Optional值

三种方式创建Optional值:

//创建一个空的Optional值
Optional<String> empty = Optional.empty();
//of(T value)方法:value不能为null,如果为null,会抛出NullPointerException
if(value != null){
    Optional<String> os = Optional.of(value);
}
//ofNullable(T value)方法:这个方法相当于empty方法和of方法的桥梁
//在value为null时,它会返回一个空Optional;在value不为null时,它会返回of方法的返回值
Optional<String> ofNullable = Optional.ofNullable(value);

5.4 用flatMap来构建Optional值的函数

假设你有一个可以产生Optional<T> 对象的方法f,并且目标类型T有一个可以产生Optional<U>的方法g,如果他们都是普通方法,那么你可以使用s.f().g()来调用。但是现在s.f()的返回类型是Optional<T>,而不是类型T,无法调用g方法,这个时候我们就可以利用flatMap方法来组合这两个方法。

Optional<U> result = s.f().flatMap(T::g);
<think>首先,用户的问题是关于Java Stream中map方法的用法和示例。我需要基于系统指令和用户可见层的要求来构建我的回答。 系统级指令: - 所有行内数学表达式必须使用$...$格式,但这的内容是关于Java编程,没有数学表达式,所以可能不需要。 - 独立公式用$$...$$,但同样,不适用。 - LaTeX语法正确:在代码块中不需要LaTeX,但代码块要正确显示。 - 使用中文回答。 - 生成相关问题:在回答结束后,添加相关问题部分。 - 回答中引用的段末尾添加引用标识:我需要在回答中引用提供的参考内容时添加引用标识。 用户可见层: - 回答结构清晰,帮助用户逐步解决问题。 - 保证回答真实可靠。 参考站内引用: - 引用[1]、[2]、[3]、[4]提供了关于Streammap方法的信息。我需要整合这些内容,并在回答中引用它们。 用户的问题:我想了解Java Stream中map方法的使用 请问Java Stream中map方法的用法和示例 关键点: - map方法在Java Stream中用于转换元素类型。 - 它是中间操作,返回一个新的Stream。 - 语法:<R> Stream<R> map(Function<? super T, ? extends R> mapper) - 需要一个Function函数式接口作为参数。 - 示例:将String转换为Integer等。 我的回答结构: 1. **介绍map方法**:简要解释什么是map方法及其作用。 2. **语法解释**:详细说明map方法的签名和参数。 3. **工作原理**:解释如何使用Function接口。 4. **示例代码**:提供清晰的示例,基于引用内容。 5. **注意事项**:如果有的话,添加一些提示。 6. **相关问题**:最后生成相关问题。 添加引用: - 在回答中,当引用具体内容时,在末尾添加[^n],例如[^1]。 示例代码: - 从引用[1]和[3]中,有类似代码: ```java Stream<String> stream = Stream.of("1", "2", "3", "4"); Stream<Integer> stream2 = stream.map(s -> Integer.parseInt(s)); stream2.forEach(System.out::println); ``` - 我可以使用这个作为基础。 相关问题: - 系统指令要求生成相关问题,放在最后。 - 例如: 1. map方法与其他Stream方法如filter的区别? 2. 如何在map方法中处理异常? 3. map方法在并行中的应用? 确保回答真实可靠:基于Java标准文档,但引用提供的内容是可靠的。 最终输出格式: - 使用中文。 - 代码块用```java```包裹。 - 回答结束后,添加“相关问题”部分。</think>### Java Stream中map方法的使用详解 Java Stream API 是 Java 8 引入的函数式编程特性,用于简化集合操作。其中,`map` 方法是一个核心中间操作,用于将中的元素映射(转换)到另一种类型,生成一个新。下面我将逐步解释其用法、语法和示例,确保回答清晰可靠。 --- #### 1. **map方法的作用和语法** - **作用**:`map` 方法接收一个函数式接口 `Function`,对中的每个元素应用该函数,将元素从类型 `T` 转换为类型 `R`,并返回一个包含转换后元素的新。例如,将字符串转换为整数或自定义对象[^1][^3]。 - **语法**: ```java <R> Stream<R> map(Function<? super T, ? extends R> mapper) ``` - `Function` 是一个函数式接口,包含抽象方法 `R apply(T t)`,用于定义转换逻辑。 - `T` 是输入元素的类型,`R` 是输出元素的类型。 - 这是一个中间操作,不会触发的执行,需配合终止操作(如 `forEach`)使用[^1][^3]。 #### 2. **工作原理** - `map` 方法遍历中的每个元素,应用 `Function` 接口的 `apply` 方法进行转换。 - 转换过程是惰性的,只有在终止操作(如 `forEach`)被调用时才执行。 - 支持链式调用,例如 `stream.map(...).filter(...).collect(...)`,实现复杂的数据处理[^2][^4]。 #### 3. **示例代码** 以下是基于实际场景的示例,展示 `map` 方法的常见用法: **示例1:基本类型转换(String → Integer)** 将字符串转换为整数: ```java import java.util.stream.Stream; public class MapExample { public static void main(String[] args) { // 创建字符串 Stream<String> stringStream = Stream.of("1", "2", "3", "4"); // 使用map转换:Function接口的Lambda表达式实现 Stream<Integer> integerStream = stringStream.map(s -> Integer.parseInt(s)); // 终止操作:遍历输出结果 integerStream.forEach(System.out::println); // 输出: 1, 2, 3, 4 } } ``` - **说明**:`map` 方法通过 `Integer.parseInt` 将每个字符串转换为整数。`s -> Integer.parseInt(s)` 是 `Function<String, Integer>` 的 Lambda 形式[^1][^3]。 **示例2:自定义对象转换** 将字符串转换为自定义对象: ```java import java.util.stream.Stream; class Person { String name; int age; Person(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return name + ":" + age; } } public class CustomMapExample { public static void main(String[] args) { // 创建字符串(格式:姓名-年龄) Stream<String> dataStream = Stream.of("Alice-25", "Bob-30", "Charlie-22"); // 使用map转换:分割字符串并创建Person对象 Stream<Person> personStream = dataStream.map(s -> { String[] parts = s.split("-"); // 分割字符串 return new Person(parts[0], Integer.parseInt(parts[1])); }); // 终止操作:输出Person对象 personStream.forEach(System.out::println); // 输出: Alice:25, Bob:30, Charlie:22 } } ``` - **说明**:此示例展示了 `map` 在复杂转换中的应用,包括字符串分割和对象创建[^2][^4]。 **示例3:链式操作(map + filter)** 结合 `filter` 方法进行过滤: ```java import java.util.stream.Stream; public class ChainedExample { public static void main(String[] args) { Stream.of("10", "20", "abc", "30") .map(s -> { try { return Integer.parseInt(s); // 尝试转换,可能抛出异常 } catch (NumberFormatException e) { return null; // 处理无效字符串 } }) .filter(i -> i != null) // 过滤掉null值 .forEach(System.out::println); // 输出: 10, 20, 30 } } ``` - **说明**:`map` 用于转换,`filter` 用于清理无效数据,体现了的链式优势[^4]。 #### 4. **注意事项** - **性能**:`map` 操作是高效的,但需避免在 `Function` 中执行耗时操作,以防影响处理性能。 - **异常处理**:在 `map` 的 Lambda 表达式中,需显式处理异常(如 `NumberFormatException`),因为函数式接口不支持直接抛出受检异常。 - **并行**:`map` 方法天然支持并行(`parallelStream()`),能自动并行化转换任务,提升大数据集处理效率[^2][^4]。 - **与 `flatMap` 区别**:`map` 是一对一转换,`flatMap` 用于一对多(如将元素展开为子),需根据需求选择[^1]。 通过以上示例,您可以掌握 `map` 方法的核心用法。Stream API 的设计简化了数据转换逻辑,提升了代码可读性和维护性[^1][^3]。 --- ### 相关问题 1. `map` 方法与 `flatMap` 方法有什么区别?请举例说明[^1]。 2. 如何在 Java Stream 中使用 `map` 方法处理异常(如无效数据转换)? 3. `map` 方法在并行(parallel stream)中如何工作?有哪些性能优化点[^4]? 4. 结合 `map` 和 `collect` 方法,如何将转换为集合(如 List 或 Map)? [^1]: Stream中的map方法基本使用和语法说明。 [^2]: map方法在自定义对象转换和链式操作中的应用案例。 [^3]: map方法的函数式接口实现和类型转换机制。 [^4]: map方法在异常处理和并行中的注意事项及优化建议。
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值