MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Java Stream collect 方法生成不同类型集合

2024-07-187.1k 阅读

Java Stream collect 方法生成不同类型集合

在Java 8引入Stream API后,极大地简化了集合操作。其中,collect方法是Stream API中非常强大的终端操作之一,它可以将流中的元素收集到各种类型的集合中。本文将深入探讨collect方法如何生成不同类型的集合,并通过丰富的代码示例帮助理解。

collect方法的基础理解

collect方法有多种重载形式,最常用的是接收一个Collector接口实现作为参数。Collector接口提供了一种灵活的方式来定义如何将流中的元素累积到一个可变的结果容器中,并且最终将这个结果容器转换为最终的结果类型。

以下是collect方法的基本调用形式:

Stream<T> stream = Stream.of(elements);
R result = stream.collect(Collector<T, A, R> collector);

这里T是流中元素的类型,A是累积过程中使用的可变容器类型,R是最终结果的类型。

生成List集合

  1. 生成ArrayList

最常见的需求之一是将流中的元素收集到ArrayList中。Java提供了Collectors.toList()方法来实现这一点。

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

public class StreamToListExample {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("apple", "banana", "cherry");
        List<String> list = stream.collect(Collectors.toList());
        System.out.println(list);
    }
}

在上述代码中,Stream.of("apple", "banana", "cherry")创建了一个包含三个字符串的流。通过调用collect(Collectors.toList()),流中的元素被收集到一个ArrayList中,并最终打印出来。

  1. 生成LinkedList

虽然Collectors.toList()默认返回ArrayList,但我们可以通过自定义Collector来生成LinkedList

import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class StreamToLinkedListExample {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("apple", "banana", "cherry");
        Collector<String, ?, List<String>> toLinkedList = Collector.of(
                LinkedList::new,
                LinkedList::add,
                (left, right) -> { left.addAll(right); return left; }
        );
        List<String> linkedList = stream.collect(toLinkedList);
        System.out.println(linkedList);
    }
}

在这段代码中,我们通过Collector.of方法自定义了一个CollectorLinkedList::new用于创建一个新的LinkedList实例,LinkedList::add用于将流中的元素添加到LinkedList中,而合并函数(left, right) -> { left.addAll(right); return left; }用于处理并行流时的合并操作。

生成Set集合

  1. 生成HashSet

Collectors.toSet()方法可以将流中的元素收集到HashSet中。HashSet不保证元素的顺序,并且会自动去除重复元素。

import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class StreamToHashSetExample {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("apple", "banana", "apple", "cherry");
        Set<String> set = stream.collect(Collectors.toSet());
        System.out.println(set);
    }
}

在上述代码中,流中包含重复的字符串“apple”,但通过collect(Collectors.toSet())收集到HashSet后,重复元素被去除。

  1. 生成TreeSet

如果需要一个有序的Set,可以使用Collectors.toCollection(TreeSet::new)来生成TreeSetTreeSet会根据元素的自然顺序或自定义比较器对元素进行排序。

import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class StreamToTreeSetExample {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("banana", "apple", "cherry");
        Set<String> treeSet = stream.collect(Collectors.toCollection(TreeSet::new));
        System.out.println(treeSet);
    }
}

在这段代码中,Stream.of("banana", "apple", "cherry")创建的流中的元素被收集到TreeSet中,并按照自然顺序(字典序)进行排序。

生成Map集合

  1. 简单的键值对映射

Collectors.toMap方法可以将流中的元素收集到Map中。它接收两个函数,一个用于提取键,另一个用于提取值。

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

public class StreamToMapExample {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("apple", "banana", "cherry");
        Map<String, Integer> map = stream.collect(
                Collectors.toMap(
                        s -> s,
                        s -> s.length()
                )
        );
        System.out.println(map);
    }
}

在上述代码中,s -> s用于提取键,即字符串本身,s -> s.length()用于提取值,即字符串的长度。最终生成的Map中,键是水果名称,值是名称的长度。

  1. 处理重复键

当流中的元素可能导致生成的Map中出现重复键时,toMap方法有一个重载形式可以处理这种情况。

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

public class StreamToMapDuplicateKeyExample {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("apple", "banana", "apple");
        Map<String, Integer> map = stream.collect(
                Collectors.toMap(
                        s -> s,
                        s -> s.length(),
                        (oldValue, newValue) -> newValue
                )
        );
        System.out.println(map);
    }
}

在这段代码中,(oldValue, newValue) -> newValue是合并函数。当遇到重复键时,会使用新值替换旧值。如果希望保留旧值,可以返回oldValue

  1. 生成TreeMap

要生成有序的Map,可以使用Collectors.toMap结合TreeMap::new

import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class StreamToTreeMapExample {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("banana", "apple", "cherry");
        Map<String, Integer> treeMap = stream.collect(
                Collectors.toMap(
                        s -> s,
                        s -> s.length(),
                        (oldValue, newValue) -> newValue,
                        TreeMap::new
                )
        );
        System.out.println(treeMap);
    }
}

这里TreeMap::new用于指定最终生成的Map类型为TreeMapTreeMap会根据键的自然顺序进行排序。

生成其他类型集合

  1. 生成Queue

与生成LinkedList类似,我们可以自定义Collector来生成Queue

import java.util.LinkedList;
import java.util.Queue;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class StreamToQueueExample {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("apple", "banana", "cherry");
        Collector<String, ?, Queue<String>> toQueue = Collector.of(
                LinkedList::new,
                Queue::add,
                (left, right) -> { left.addAll(right); return left; }
        );
        Queue<String> queue = stream.collect(toQueue);
        System.out.println(queue);
    }
}

在这段代码中,通过自定义Collector,将流中的元素收集到Queue(这里是LinkedList实现的Queue)中。

  1. 生成自定义集合类型

假设我们有一个自定义的集合类型MyCustomCollection,并且它有相应的构造函数和添加元素的方法。

import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;

class MyCustomCollection<T> {
    private StringBuilder stringBuilder = new StringBuilder();

    public void add(T element) {
        stringBuilder.append(element).append(", ");
    }

    @Override
    public String toString() {
        if (stringBuilder.length() > 0) {
            stringBuilder.setLength(stringBuilder.length() - 2);
        }
        return stringBuilder.toString();
    }
}

public class StreamToCustomCollectionExample {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("apple", "banana", "cherry");
        Collector<String, ?, MyCustomCollection<String>> toCustomCollection = Collector.of(
                MyCustomCollection::new,
                MyCustomCollection::add,
                (left, right) -> { left.add(right.toString()); return left; }
        );
        MyCustomCollection<String> customCollection = stream.collect(toCustomCollection);
        System.out.println(customCollection);
    }
}

在上述代码中,我们自定义了一个MyCustomCollection类型,它将元素以逗号分隔的形式存储在StringBuilder中。通过自定义Collector,将流中的元素收集到MyCustomCollection实例中。

并行流与collect方法

当使用并行流时,collect方法中的Collector的合并操作变得尤为重要。以生成ArrayList为例,并行流会将元素分块处理,然后合并这些部分结果。

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

public class ParallelStreamToListExample {
    public static void main(String[] args) {
        Stream<String> parallelStream = Stream.of("apple", "banana", "cherry").parallel();
        List<String> list = parallelStream.collect(Collectors.toList());
        System.out.println(list);
    }
}

在这个例子中,Stream.of("apple", "banana", "cherry").parallel()创建了一个并行流。Collectors.toList()内部的Collector实现了正确的合并逻辑,确保并行处理的结果与顺序处理的结果一致。

总结不同集合类型的选择

  1. List

    • ArrayList:适用于需要快速随机访问的场景,因为它基于数组实现。但在频繁插入和删除元素时性能较差,特别是在列表中间位置操作时。
    • LinkedList:适合频繁插入和删除元素的场景,因为它基于链表结构。但随机访问性能不如ArrayList
  2. Set

    • HashSet:用于需要快速查找和去除重复元素的场景,不保证元素顺序。
    • TreeSet:适用于需要元素有序的场景,它会根据自然顺序或自定义比较器对元素进行排序。
  3. Map

    • HashMap:提供了快速的键值对查找和插入操作,不保证键的顺序。
    • TreeMap:适用于需要按键排序的场景,无论是自然顺序还是自定义顺序。
  4. 其他集合

    • Queue:用于需要按照特定顺序处理元素的场景,如先进先出(FIFO)。
    • 自定义集合:当标准集合类型不能满足特定需求时,可以创建自定义集合类型并通过collect方法收集元素。

通过深入理解collect方法如何生成不同类型的集合,并根据实际需求选择合适的集合类型,开发者可以充分利用Java Stream API的强大功能,编写高效、简洁的代码。无论是处理大规模数据集还是简单的集合操作,collect方法都为我们提供了丰富的可能性。在实际应用中,要根据数据的特点、操作的频率以及性能要求等因素综合考虑选择合适的集合类型和collect方法的使用方式。同时,并行流的使用可以进一步提升处理大数据集时的效率,但需要注意Collector的合并操作是否正确实现,以确保结果的准确性。