一行初始化 ArrayList

我想创建用于测试目的的选项列表。首先,我这样做:

ArrayList<String> places = new ArrayList<String>();
places.add("Buenos Aires");
places.add("Córdoba");
places.add("La Plata");

然后,我将代码重构如下:

ArrayList<String> places = new ArrayList<String>(
    Arrays.asList("Buenos Aires", "Córdoba", "La Plata"));

有一个更好的方法吗?

答案

如果仅将其声明为List ,则将更简单 - 它必须是 ArrayList 吗?

List<String> places = Arrays.asList("Buenos Aires", "Córdoba", "La Plata");

或者,如果您只有一个元素:

List<String> places = Collections.singletonList("Buenos Aires");

这意味着places不变的 (尝试更改位置将导致引发UnsupportedOperationException异常)。

要创建一个具体的ArrayList可变列表,您可以从不可变列表创建ArrayList

ArrayList<String> places = new ArrayList<>(Arrays.asList("Buenos Aires", "Córdoba", "La Plata"));

实际上,初始化ArrayList的 “最佳” 方法可能是您编写的方法,因为它不需要以任何方式创建新的List

ArrayList<String> list = new ArrayList<String>();
list.add("A");
list.add("B");
list.add("C");

问题是引用该list实例需要大量输入。

还有其他选择,例如使用实例初始化程序(也称为 “双括号初始化”)创建匿名内部类:

ArrayList<String> list = new ArrayList<String>() {{
    add("A");
    add("B");
    add("C");
}};

但是,我不太喜欢这种方法,因为最终得到的是ArrayList的子类,该子类具有实例初始化程序,并且创建该类只是为了创建一个对象 - 对我来说似乎有点过头了。

如果接受了针对Project CoinCollection Literals 提案 (原定于 Java 7 中引入,但也不太可能成为 Java 8 的一部分),那将是一个很好的选择:

List<String> list = ["A", "B", "C"];

不幸的是,它在这里无济于事,因为它将初始化一个不可变的List而不是ArrayList ,而且,如果有的话,它还不可用。

简单的答案

在 Java 10 或更高版本中:

var strings = List.of("foo", "bar", "baz");

在 Java 9 或更高版本中:

List<String> strings = List.of("foo", "bar", "baz");

这将为您提供一个不变的List ,因此无法更改。
在大多数情况下,这是您想要的。


Java 8 或更早版本:

List<String> strings = Arrays.asList("foo", "bar", "baz");

这将为您提供由数组支持的List ,因此它不能更改长度。
但是您可以调用List.set ,因此它仍然是可变的。


您可以使用静态导入使Arrays.asList变得更短:

List<String> strings = asList("foo", "bar", "baz");

静态导入:

import static java.util.Arrays.asList;

任何现代 IDE 都会建议并自动为您执行的操作。
例如,在 IntelliJ IDEA 中,按Alt+Enter并选择 “ Static import method...


但是,我不建议缩短 Java 9 List.of方法,因为只有of会造成混淆。
List.of已经足够短了,而且读起来也不错。


使用Stream

为什么它必须是List
在 Java 8 或更高版本中,您可以使用更灵活的Stream

Stream<String> strings = Stream.of("foo", "bar", "baz");

您可以串联Stream

Stream<String> strings = Stream.concat(Stream.of("foo", "bar"),
                                       Stream.of("baz", "qux"));

或者,您可以从Stream转到List

import static java.util.stream.Collectors.toList;

List<String> strings = Stream.of("foo", "bar", "baz").collect(toList());

但最好只是使用Stream而不将其收集到List


如果您确实确实需要java.util.ArrayList

(您可能没有。)
引用JEP 269 (重点是我的话):

很少的用例 ,用于初始化具有预定义值集的可变集合实例。通常最好将这些预定义值放在不可变的集合中,然后通过复制构造函数初始化可变集合。


如果您要预填充一个ArrayList 然后ArrayList加到它(为什么?),请使用

ArrayList<String> strings = new ArrayList<>(List.of("foo", "bar"));
strings.add("baz");

或在 Java 8 或更早版本中:

ArrayList<String> strings = new ArrayList<>(asList("foo", "bar"));
strings.add("baz");

或使用Stream

import static java.util.stream.Collectors.toCollection;

ArrayList<String> strings = Stream.of("foo", "bar")
                             .collect(toCollection(ArrayList::new));
strings.add("baz");

但是同样,最好直接使用Stream而不是将其收集到List


程序接口,而不是实现

您说过已在代码中将列表声明为ArrayList ,但是仅当使用的不是ListArrayList成员时,才应该这样做。

您最有可能不这样做。

通常,您只应通过将要使用的最通用的接口(例如IterableCollectionList )声明变量,并使用特定的实现(例如ArrayListLinkedListArrays.asList() )对其进行初始化。

否则,您会将代码限制为该特定类型,并且在需要时将很难更改。

例如,如果要将ArrayList传递给void method(...)

// Iterable if you just need iteration, for (String s : strings):
void method(Iterable<String> strings) { 
    for (String s : strings) { ... } 
}

// Collection if you also need .size(), .isEmpty(), or .stream():
void method(Collection<String> strings) {
    if (!strings.isEmpty()) { strings.stream()... }
}

// List if you also need .get(index):
void method(List<String> strings) {
    strings.get(...)
}

// Don't declare a specific list implementation
// unless you're sure you need it:
void method(ArrayList<String> strings) {
    ??? // You don't want to limit yourself to just ArrayList
}

另一个示例将始终将变量声明为InputStream即使它通常是FileInputStreamBufferedInputStream ,因为有一天您或其他人将想要使用其他类型的InputStream

如果您需要简单的尺寸清单 1:

List<String> strings = new ArrayList<String>(Collections.singletonList("A"));

如果您需要几个对象的列表:

List<String> strings = new ArrayList<String>();
Collections.addAll(strings,"A","B","C","D");

使用番石榴,您可以编写:

ArrayList<String> places = Lists.newArrayList("Buenos Aires", "Córdoba", "La Plata");

在 Guava 中,还有其他有用的静态构造函数。您可以在这里阅读有关它们的信息

集合文字没有将其纳入 Java 8,但是可以使用 Stream API 在很长的一行中初始化列表:

List<String> places = Stream.of("Buenos Aires", "Córdoba", "La Plata").collect(Collectors.toList());

如果需要确保您的ListArrayList

ArrayList<String> places = Stream.of("Buenos Aires", "Córdoba", "La Plata").collect(Collectors.toCollection(ArrayList::new));
import com.google.common.collect.ImmutableList;

....

List<String> places = ImmutableList.of("Buenos Aires", "Córdoba", "La Plata");

使用及更高版本(如JDK 增强建议 - 269 中所建议) ,现在可以使用集合文字与 -

List<String> list = List.of("A", "B", "C");

Set<String> set = Set.of("A", "B", "C");

类似的方法也适用于Map

Map<String, String> map = Map.of("k1", "v1", "k2", "v2", "k3", "v3")

这类似于 @coobird 所说的 Collection Literals 建议 。 JEP 中也进一步阐明了 -


备择方案

语言更改已被考虑多次,但被拒绝:

项目硬币提案,2009 年 3 月 29 日

项目硬币提案,2009 年 3 月 30 日

关于 lambda-dev 的 JEP 186 讨论,2014 年 1 月至 3 月

语言建议比该消息中概述的基于库的建议优先放置

相关: Java 9 中用于集合的重载便利工厂方法的重点是什么?

您可以创建一个工厂方法:

public static ArrayList<String> createArrayList(String ... elements) {
  ArrayList<String> list = new ArrayList<String>();
  for (String element : elements) {
    list.add(element);
  }
  return list;
}

....

ArrayList<String> places = createArrayList(
  "São Paulo", "Rio de Janeiro", "Brasília");

但这并不比您的第一个重构好多少。

为了更大的灵活性,它可以是通用的:

public static <T> ArrayList<T> createArrayList(T ... elements) {
  ArrayList<T> list = new ArrayList<T>();
  for (T element : elements) {
    list.add(element);
  }
  return list;
}

在 Java 9 中,我们可以轻松地在一行中初始化ArrayList

List<String> places = List.of("Buenos Aires", "Córdoba", "La Plata");

要么

List<String> places = new ArrayList<>(List.of("Buenos Aires", "Córdoba", "La Plata"));

与以前的方法相比,Java 9 的这种新方法具有许多优点:

  1. 空间效率
  2. 不变性
  3. 线程安全

请参阅此帖子以获取更多详细信息 -> List.of 和 Arrays.asList 有什么区别?