1. 概述

在这个教程中,我们将展示如何将多个集合合并成一个逻辑上的集合。我们将探讨五种不同的方法——两种使用Java 8,一种使用Guava,一种使用Apache Commons Collections,以及仅使用标准的Java 7 SDK。

接下来的例子中,我们考虑以下集合:

Collection<String> collectionA = Arrays.asList("S", "T");
Collection<String> collectionB = Arrays.asList("U", "V");

2. 使用Java 8流API

Java API的Stream接口提供了处理集合时非常有用的工具方法,使操作更加便捷。我们将看看其中的两个方法——concat()flatMap()——用于组合集合。

一旦获得一个Stream,你就可以在其上执行聚合操作。

2.1. 使用concat()方法

concat()静态方法通过创建一个惰性连接的Stream,将两个Stream逻辑上合并,其元素顺序是第一个Stream的所有元素后跟第二个Stream的所有元素。

下面的例子演示了如何使用concat()方法将collectionAcollectionB合并:

Stream<String> combinedStream = Stream.concat(
  collectionA.stream(),
  collectionB.stream());

如果你需要合并超过两个的Stream,可以在原始调用中再次使用concat()方法:

Stream<String> combinedStream = Stream.concat(
  Stream.concat(collectionA.stream(), collectionB.stream()), 
  collectionC.stream());

请注意,Java 8的Stream不可重用,所以在将它们赋值给变量时请考虑这一点。

2.2. 使用flatMap()方法

flatMap()方法在每个元素上返回一个新的Stream,该Stream的元素是通过应用提供的映射函数到每个元素产生的映射Stream的内容。

下面的示例展示了使用flatMap()方法合并集合的过程。首先,你得到一个元素是两个集合的Stream,然后扁平化这个Stream并收集它到一个合并的列表中:

Stream<String> combinedStream = Stream.of(collectionA, collectionB)
  .flatMap(Collection::stream);
Collection<String> collectionCombined = 
  combinedStream.collect(Collectors.toList());

3. 使用Guava

Google的Guava库提供了许多方便的方法来操作集合,适用于Java 6或更高版本。

3.1. 使用Iterables.concat()方法

Iterables.concat()是Guava提供的一种便利方法,用于合并集合:

Iterable<String> combinedIterables = Iterables.unmodifiableIterable(
  Iterables.concat(collectionA, collectionA));

返回的Iterable可以转换为一个集合:

Collection<String> collectionCombined = Lists.newArrayList(combinedIterables);

3.2. Maven依赖

在你的Maven pom.xml文件中添加以下依赖,以在项目中包含Guava库:

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>32.1.3-jre</version>
</dependency>

你可以在Maven中央仓库找到Guava库的最新版本。

4. 使用Apache Commons Collections

Apache Commons Collections是另一个帮助处理各种集合的工具库。该库提供了两个可用于合并集合的实用方法。本节将介绍这些方法的工作原理。

4.1. 使用IterableUtils.chainedIterable()方法

IterableUtils类为Iterable实例提供实用方法和装饰器。它提供了chainedIterable()方法,可以将多个Iterable合并成一个单一的Iterable

Iterable<String> combinedIterables = IterableUtils.chainedIterable(
  collectionA, collectionB);

4.2. 使用CollectionUtils.union()方法

CollectionUtils类为Collection实例提供实用方法和装饰器。这个类的union()方法返回一个包含给定Iterable实例的并集的Collection

Iterable<String> combinedIterables = CollectionUtils.union(
  collectionA, collectionB);

对于union()方法,返回集合中每个元素的基数等于给定两个Iterables中相应元素的最大基数。这意味着合并后的集合只包含第一个集合中的元素以及第二个集合中不在第一个集合中的元素。

4.3. Maven依赖

在你的Maven pom.xml文件中添加以下依赖,以在项目中包含Apache Commons Collections库:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-collections4</artifactId>
    <version>4.1</version>
</dependency>

你可以在Maven中央仓库找到Apache Commons库的最新版本。

5. 使用Java 7

如果你仍在使用Java 7,并希望避免使用像Guava这样的第三方库,你可以使用addAll()方法来合并来自多个集合的元素,或者编写自己的实用方法来组合Iterables

5.1. 使用addAll()方法

当然,最简单的合并集合的方法是使用addAll()方法,如以下List示例所示。但值得注意的是,这种方法会创建一个新的集合,并为第一个两个集合中的相同对象添加额外的引用:

List<String> listC = new ArrayList<>();
listC.addAll(listA);
listC.addAll(listB);

5.2. 编写自定义concat()方法

下面的示例定义了一个concat()方法,接受两个Iterables并返回一个合并的Iterable对象:

public static <E> Iterable<E> concat(
  Iterable<? extends E> i1,
  Iterable<? extends E> i2) {
        return new Iterable<E>() {
            public Iterator<E> iterator() {
                return new Iterator<E>() {
                    Iterator<? extends E> listIterator = i1.iterator();
                    Boolean checkedHasNext;
                    E nextValue;
                    private boolean startTheSecond;

                    void theNext() {
                        if (listIterator.hasNext()) {
                            checkedHasNext = true;
                            nextValue = listIterator.next();
                        } else if (startTheSecond)
                            checkedHasNext = false;
                        else {
                            startTheSecond = true;
                            listIterator = i2.iterator();
                            theNext();
                        }
                    }

                    public boolean hasNext() {
                        if (checkedHasNext == null)
                            theNext();
                        return checkedHasNext;
                    }

                    public E next() {
                        if (!hasNext())
                            throw new NoSuchElementException();
                        checkedHasNext = null;
                        return nextValue;
                    }

                    public void remove() {
                        listIterator.remove();
                    }
                };
            }
        };
    }

可以通过传递两个集合作为参数来调用concat()方法:

Iterable<String> combinedIterables = concat(collectionA, collectionB);
Collection<String> collectionCombined = makeListFromIterable(combinedIterables);

如果你想让Iterable可用作List,也可以使用makeListFromIterable()方法,它使用Iterable的成员创建一个List

public static <E> List<E> makeListFromIterable(Iterable<E> iter) {
    List<E> list = new ArrayList<E>();
    for (E item : iter) {
        list.add(item);
    }
    return list;
}

6. 总结

本文讨论了多种在Java中不创建额外对象引用的情况下逻辑合并两个集合的方法。

本教程的代码可在GitHub上获取。