概述

本文将探讨如何将给定的ArrayList<Object>转换为ArrayList<String>的各种方法。

问题描述

设想我们有一个ArrayList<Object>,其中的对象可以是任何类型,包括自动装箱的基本类型(如IntegerFloatBoolean),非基本引用类型(如StringArrayListHashMap,甚至是自定义定义的类)。我们需要编写代码,将上述列表转换为ArrayList<String>让我们看几个例子:

Example 1: [1, 2, 3, 4, 5]
Output 1: ["1", "2", "3", "4", "5"]

Example 2: ["Hello", 4.9837, -19837338737, true]
Output 2: ["Hello", "4.9837", "-19837338737", "true"]

Example 3: [new Node(1,4), new Double(7699.05), new User("John Doe")]
Output 3: ["Node (x=1, y=4)", "7699.05", "User (full name=John Doe)"]

输入列表可以包含各种对象,如自定义类UserNode的实例。假设这些类已重写了toString()方法。如果没有定义,将调用Object类的toString()方法,输出如下:

Node@f6d9f0, User@u8g0f9

以上示例包含了自定义类UserNode的实例:

public class User {
    private final String fullName;

    // getters and setters

   @Override
    public String toString() {
        return "User (" + "full name='" + fullName + ')';
    }
}
public class Node {
    private final int x;
    private final int y;

    // getters and setters

    @Override
    public String toString() {
        return "Node (" + "x=" + x + ", y=" + y + ')';
    }
}

假设在接下来的部分中,变量inputListexpectedStringList引用了我们的目标输入和输出列表:

List<Object> inputList = List.of(
                        1,
                        true,
                        "hello",
                        Double.valueOf(273773.98),
                        new Node(2, 4),
                        new User("John Doe")
                    );
List<String> expectedStringList = List.of(
                        "1",
                        "true",
                        "hello",
                        Double.toString(273773.98),
                        new Node(2, 4).toString(),
                        new User("John Doe").toString()
                    );

使用Java集合遍历转换

首先尝试使用Java集合来解决这个问题。思路是遍历列表中的每个元素,并将其转换为String。完成后,我们就得到了一个String对象的列表。下面的代码使用for-each循环遍历给定的列表,并通过调用toString()方法明确地将每个对象转换为String

List<String> outputList = new ArrayList<>(inputList.size());
for(Object obj : inputList){
    outputList.add(obj.toString());
}
Assert.assertEquals(expectedStringList, outputList);

这个解决方案适用于输入列表中所有对象组合,并且对所有Java版本(Java 5及以上)都有效。然而,上述解决方案无法免疫输入中的null对象,遇到null时会抛出NullPointerException。一个简单的改进利用了Java 7引入的Objects工具类提供的toString()方法,使其对null安全:

List<String> outputList = new ArrayList<>(inputList.size());
for(Object obj : inputList){
    outputList.add(Objects.toString(obj, null));
}
Assert.assertEquals(expectedStringList, outputList);

使用Java流转换

我们也可以利用Java 8的流API来解决问题。首先,我们将数据源inputList转换为流,通过应用stream()方法。当我们有了一个元素类型为Object的流后,我们需要一个中间操作,即对象到字符串的转换,最后是收集结果到另一个String类型的列表的终端操作。

中间操作在我们的例子中是一个map()操作,它接受一个lambda表达式:

(obj) -> Objects.toString(obj, null)

流需要一个终端操作来编译并返回所需的列表。在接下来的子节中,我们将讨论可用的不同终端操作。

4.1. Collectors.toList()作为终端操作

在这个方法中,我们使用Collectors.toList()将由中间操作生成的流收集到输出列表中:

List<String> outputList;
outputList = inputList
    .stream()
    .map((obj) -> Objects.toString(obj, null))
    .collect(Collectors.toList());
Assert.assertEquals(expectedStringList, outputList);

这种方法适用于Java 8及更高版本,因为流API在Java 8中引入。输出的列表是可变的,意味着我们可以向其中添加元素。输出列表也可能包含null值。

4.2. Collectors.toUnmodifableList()作为终端操作 - Java 10兼容方法

如果我们想要生成一个不可修改的String对象列表,我们可以利用Java 10中引入的Collectors.toUnmodifableList()实现:

List<String> outputList;
outputList = inputList
    .stream()
    .filter(Objects::nonNull)
    .map((obj) -> Objects.toString(obj, null))
    .collect(Collectors.toUnmodifiableList());
Assert.assertEquals(expectedStringListWithoutNull, outputList);

这里的一个重要限制是列表不能包含null值,因此如果inputList包含null,这段代码会产生NullPointerException。这就是为什么我们在应用操作之前先从流中选择只包含非null元素的原因。输出列表是不可变的,如果尝试后续修改,将抛出UnsupportedOperationException

4.3. toList()作为终端操作 - Java 16兼容方法

如果想直接从输入流创建一个允许包含null值的不可修改列表,可以使用Java 16中在Stream接口中引入的toList()方法:

List<String> outputList;
outputList = inputList
    .stream()
    .map((obj) -> Objects.toString(obj, null))
    .toList();
Assert.assertEquals(expectedStringList, outputList);

使用Guava转换

我们可以使用Google的Guava库将对象输入列表转换为String的新列表。

5.1. Maven配置

为了使用Guava库,我们需要在pom.xml中添加相应的Maven依赖:

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>33.0.0-jre</version>
    <scope>test</scope>
</dependency>

可以从Maven中央仓库获取最新版本的依赖。

5.2. 使用Lists.transform()

我们可以使用Guava的Lists.transform()方法。它接受inputList和前面提到的lambda表达式,生成String对象的输出列表:

List<String> outputList;
outputList = Lists.transform(inputList, obj -> Objects.toString(obj, null));
Assert.assertEquals(expectedStringList, outputList);

使用此方法,输出列表可以包含null值。

总结

本文探讨了将ArrayList<Object>元素转换为ArrayList<String>的几种不同方式,从基于for-each循环的方法到Java流的方法。还研究了针对不同Java版本的特定实现,以及使用Guava的方法。如往常一样,所有的代码示例可以在GitHub上找到。