有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

类构造函数中的java ArrayList字段

我目前正在学习初级Java,在构造函数中将ArrayList用作参数时遇到了一个问题

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

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

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


共 (1) 个答案

  1. # 1 楼答案

    这是有区别的,是的。在给出的代码中,可以这样调用构造函数:

    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中是不可能的