class 构造函数中的 ArrayList 字段
ArrayList fields in class constructors
我目前正在学习初级 Java,在构造函数中用作参数时遇到 ArrayList
问题。
当我必须在 class 构造函数中初始化一个 ArrayList
时,我通常会这样写(为了这个例子,假设我想创建一个 ArrayList
的整数作为 class 字段)。
public class Example {
private ArrayList<Integer> myList;
public Example(ArrayList<Integer> myInts){
this.myList = myInts;
}
}
然而,当我看到有人在教程或教科书中做同样的事情时,他们会编写以下代码:
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);
}
}
这两个例子有区别吗?我认为我的做法是错误的,但实际上 运行 两个版本都给我相同的结果(就我有限的理解而言),所以我很难理解是什么让这两个变体与众不同。
有区别,是的。在提供的代码中,可以这样调用构造函数:
ArrayList<Integer> values = new ArrayList<>(List.of(1, 2, 3, 4));
Example example = new Example(values);
对象构造完成后,调用方仍然可以访问values
,即example
使用的内部数据结构。通过操纵此数据结构,调用方可能会使 example
处于意外状态并导致问题。
为了避免此类问题,我们通常不会直接使用从外部传入的引用类型作为内部状态,而是生成它们的副本。从本质上讲,这就是第二个示例所做的。如果我们仍然想将 List
作为参数传递给构造函数,我们可以复制列表:
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()));
}
}
现在,如果调用方改变传递给构造函数的 List
,Example
实例中的内部数据结构不受影响,因为它对原始列表的副本进行操作。
备注:如果列表类型是可变的,那么复制列表通常是不够的;我们将不得不深度复制列表(即创建每个列表条目的副本)。这通常在 Java.
中是不可能的
我目前正在学习初级 Java,在构造函数中用作参数时遇到 ArrayList
问题。
当我必须在 class 构造函数中初始化一个 ArrayList
时,我通常会这样写(为了这个例子,假设我想创建一个 ArrayList
的整数作为 class 字段)。
public class Example {
private ArrayList<Integer> myList;
public Example(ArrayList<Integer> myInts){
this.myList = myInts;
}
}
然而,当我看到有人在教程或教科书中做同样的事情时,他们会编写以下代码:
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);
}
}
这两个例子有区别吗?我认为我的做法是错误的,但实际上 运行 两个版本都给我相同的结果(就我有限的理解而言),所以我很难理解是什么让这两个变体与众不同。
有区别,是的。在提供的代码中,可以这样调用构造函数:
ArrayList<Integer> values = new ArrayList<>(List.of(1, 2, 3, 4));
Example example = new Example(values);
对象构造完成后,调用方仍然可以访问values
,即example
使用的内部数据结构。通过操纵此数据结构,调用方可能会使 example
处于意外状态并导致问题。
为了避免此类问题,我们通常不会直接使用从外部传入的引用类型作为内部状态,而是生成它们的副本。从本质上讲,这就是第二个示例所做的。如果我们仍然想将 List
作为参数传递给构造函数,我们可以复制列表:
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()));
}
}
现在,如果调用方改变传递给构造函数的 List
,Example
实例中的内部数据结构不受影响,因为它对原始列表的副本进行操作。
备注:如果列表类型是可变的,那么复制列表通常是不够的;我们将不得不深度复制列表(即创建每个列表条目的副本)。这通常在 Java.
中是不可能的