类构造函数中的 ArrayList 字段

Car*_*ani 5 java methods constructor field arraylist

我目前正在学习初级 Java 并且ArrayList在构造函数中用作参数时遇到了问题。

当我必须ArrayList在类构造函数中初始化 an时,我通常会写这样的东西(为了这个例子,假设我想创建一个ArrayList整数作为类字段)。

public class Example {

    private ArrayList<Integer> myList;

    public Example(ArrayList<Integer> myInts){
        this.myList = myInts;
    }
       
}
Run Code Online (Sandbox Code Playgroud)

但是,当我看到人们在教程或教科书中做同样的事情时,他们会编写以下代码:

public class Example {

    private ArrayList<Integer> myList;

    public Example(int myInts){
        this.myList = new ArrayList<>();
        addIntegers(myInts);
    }
 
    public void addIntegers(int myInts){
        this.myList.add(myInts);
    }
      
}
Run Code Online (Sandbox Code Playgroud)

这两个例子有区别吗?我认为我的方法是错误的,但实际上运行这两个版本给了我相同的结果(就我有限的理解而言),所以我很难理解是什么让这两个变体与众不同。

Tur*_*g85 2

是的,有区别。在所提供的代码中,可以像这样调用构造函数:

ArrayList<Integer> values = new ArrayList<>(List.of(1, 2, 3, 4));
Example example = new Example(values);
Run Code Online (Sandbox Code Playgroud)

对象构造完成后,调用方仍然可以访问values,即 所使用的内部数据结构example。通过操纵此数据结构,调用方可能会导致example意外状态并导致问题。

为了防止此类问题,我们通常不会直接使用从外部传入的引用类型作为内部状态,而是生成它们的副本。从本质上讲,这就是第二个示例的作用。如果我们仍然想将 aList作为参数传递给构造函数,我们可以复制列表:

public class Example {

    private ArrayList<Integer> myList;

    public Example(Collection<Integer> myInts) {
        this.myList = new ArrayList<>(Objects.requireNonNull(myInts));
    }

    public Example(Integer... myInts) {
        this(Arrays.asList(Objects.requireNonNull(myInts)));
    }

    public Example(int... myInts) {
        this(Arrays.stream(Objects.requireNonNull(myInts))
                .boxed()
                .collect(Collectors.toList()));
    }           
}
Run Code Online (Sandbox Code Playgroud)

现在,如果调用方改变List传递给构造函数的数据,则实例中的内部数据结构Example不会受到影响,因为它对原始列表的副本进行操作。

备注:如果列表类型是可变的,那么复制列表通常是不够的;我们必须深度复制列表(即创建每个列表条目的副本)。一般来说,这在 Java 中是不可能的。