我想创建一个用于测试的选项列表。起初,我这样做:

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"));

有更好的方法吗?


当前回答

Java 9有以下方法来创建不可变列表:

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

如果需要,它很容易适于创建可变列表:

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

类似的方法可用于“集”和“贴图”。

其他回答

您可以使用Cactoos中的粘滞列表:

List<String> names = new StickyList<>(
  "Scott Fitzgerald", "Fyodor Dostoyevsky"
);

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

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

问题在于,引用该列表实例需要大量的输入。

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

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

然而,我并不太喜欢这个方法,因为你最终得到的是ArrayList的一个子类,它有一个实例初始化器,而创建这个类只是为了创建一个对象——这在我看来有点过分。

如果Project Coin的Collection Literals提案被接受(它计划在Java 7中引入,但也不太可能是Java 8的一部分),那将是一件好事

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

不幸的是,它在这里对您没有帮助,因为它将初始化一个不可变的List,而不是ArrayList,此外,它还不可用,如果可能的话。

对于java-9和更高版本,正如JEP269:集合的便利工厂方法中所建议的,这可以使用集合文本实现,现在使用-

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所述的“收藏文字”提案类似。JEP中也有进一步澄清-


选择

语言更改已被考虑过多次,但均被拒绝:项目硬币提案,2009年3月29日项目硬币提案,2009年3月30日JEP 186关于lambda开发的讨论,2014年1月至3月语言建议被搁置,而不是基于图书馆的建议,因为此消息中总结。

相关:在Java9中重载集合的便利工厂方法有什么意义

如果您需要大小为1的简单列表:

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

如果需要多个对象的列表:

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

简单的答案

Java 9或更高版本:

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

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

Java 8或更早版本:

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

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

*实现细节:它是java.util.Arrays内部的一个私有嵌套类,名为ArrayList,这是一个与java.util.ArrayList不同的类,尽管它们的简单名称相同。

静态导入

您可以通过静态导入使Java 8 Arrays.asList更短:

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

任何现代IDE*都会为您建议并执行此操作。

我不建议静态地导入List.of方法,因为它会令人困惑。

*例如,在IntelliJ IDEA中,按Alt+Enter并选择静态导入方法。。。

使用流

为什么必须是列表?对于Java 8或更高版本,您可以使用更灵活的Stream:

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

您可以连接流:

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

或者您可以从“流”转到“列表”:

import static java.util.stream.Collectors.toList;
...
var strings = Stream.of("foo", "bar", "baz").toList(); // Java 16

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

但最好只使用流而不将其收集到列表中。

如果您特别需要java.util.ArrayList*

如果要预先填充ArrayList并在之后添加到其中,请使用

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

或Java 8或更早版本:

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

或使用流:

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

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

但同样,最好直接使用流,而不是将其收集到列表中。

*您可能不需要特别的ArrayList。引用JEP 269:

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

(强调矿井)

程序到接口,而不是实现

您说过您已经在代码中将列表声明为ArrayList,但只有当您使用的ArrayList的某个成员不在list中时,才应该这样做。

这是你最可能不做的。

通常,您应该只通过要使用的最通用的接口(例如Iterable、Collection或List)声明变量,并使用特定的实现(例如ArrayList、LinkedList或Arrays.asList())初始化它们。

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

例如,如果要将ArrayList传递给void方法(…):

// 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 random access, .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,即使它通常是FileInputStream或BufferedInputStream,因为很快有一天你或其他人会想要使用其他类型的InputStream。