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

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> 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。

其他回答

使用Array.asList(“布宜诺斯艾利斯”、“科尔多瓦”、“拉普拉塔”);是正确的。但任何对Arrays.asList()的调用如果没有参数或只有一个参数,都可以用对Collections.singletonList()或Collections.emptyList()进行替换,这样可以节省一些内存。

注意:Collections.singletonList()返回的列表是不可变的,而列表返回的Arrays.asList()允许调用set()方法。在极少数情况下,这可能会破坏代码。

简单的答案

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。

import com.google.common.collect.ImmutableList;

....

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

您可以创建工厂方法:

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中,您无法做到

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

如前所述,您需要执行双括号初始化:

List<String> places = new ArrayList<String>() {{ add("x"); add("y"); }};

但这可能会迫使您添加注释@SuppressWarnings(“串行”)或生成一个令人讨厌的串行UUID。大多数代码格式化程序也会将其展开为多个语句/行。

或者你也可以这样做

List<String> places = Arrays.asList(new String[] {"x", "y" });

但您可能需要执行@SuppressWarnings(“未选中”)。

此外,根据javadoc,您应该能够做到这一点:

List<String> stooges = Arrays.asList("Larry", "Moe", "Curly");

但我无法用JDK1.6编译它。