List集合拷贝讨论

667 阅读2分钟

今天在检查自己写的代码(bug)时,通过调试,发现了bug产生的原因原来是集合拷贝问题。修改拷贝后的集合属性时,源集合的属性也被修改了,我是通过new ArrayList(原集合)这种方式来拷贝的。于是我尝试对代码进行修改,历经多次调整,最后我改成了这样,成功了(苦笑):

Snipaste_2022-01-20_16-08-25.png

List<InsightOutputDiffSumMain> list = new ArrayList<>();
		for (InsightOutputDiffSumMain in:incomeList){
			InsightOutputDiffSumMain sumMain = new InsightOutputDiffSumMain(); //new一个对象
			sumMain.setZero(new InsightOutputDiffSumTaxRateDetail(in.getZero().getPrice(),in.getZero().getDiff()));
			sumMain.setZeroPointFive(new InsightOutputDiffSumTaxRateDetail(in.getOnePointFive().getPrice(),in.getOnePointFive().getDiff()));
			sumMain.setOne(new InsightOutputDiffSumTaxRateDetail(in.getOne().getPrice(),in.getOne().getDiff()));
			sumMain.setOnePointFive(new InsightOutputDiffSumTaxRateDetail(in.getOnePointFive().getPrice(),in.getOnePointFive().getDiff()));
			sumMain.setTwo(new InsightOutputDiffSumTaxRateDetail(in.getTwo().getPrice(),in.getTwo().getDiff()));
			sumMain.setThree(new InsightOutputDiffSumTaxRateDetail(in.getThree().getPrice(),in.getThree().getDiff()));
			sumMain.setFour(new InsightOutputDiffSumTaxRateDetail(in.getFour().getPrice(),in.getFour().getDiff()));
			sumMain.setFive(new InsightOutputDiffSumTaxRateDetail(in.getFive().getPrice(),in.getFive().getDiff()));
			sumMain.setSix(new InsightOutputDiffSumTaxRateDetail(in.getSix().getPrice(),in.getSix().getDiff()));
			sumMain.setNine(new InsightOutputDiffSumTaxRateDetail(in.getNine().getPrice(),in.getNine().getDiff()));
			sumMain.setTen(new InsightOutputDiffSumTaxRateDetail(in.getTen().getPrice(),in.getTen().getDiff()));
			sumMain.setEleven(new InsightOutputDiffSumTaxRateDetail(in.getEleven().getPrice(),in.getEleven().getDiff()));
			sumMain.setThirteen(new InsightOutputDiffSumTaxRateDetail(in.getThirteen().getPrice(),in.getThirteen().getDiff()));
			sumMain.setSixteen(new InsightOutputDiffSumTaxRateDetail(in.getSixteen().getPrice(),in.getSixteen().getDiff()));
			sumMain.setSeventeen(new InsightOutputDiffSumTaxRateDetail(in.getSeventeen().getPrice(),in.getSeventeen().getDiff()));
			sumMain.setProjectName(in.getProjectName());
			list.add(sumMain);
		}

因为集合元素的属性是对象,所以我需要拷贝对象的所有属性,还好属性只有2个。 看到这段代码我不禁怀疑自己,所以我开始寻求更简洁的解决方法,于是我就去了解学习了集合拷贝,分享给大家。

List集合的几种拷贝方式

  • new ArrayList()
  • Object.clone()
  • BeanUtils.copyProperties()
  • Collections.copy()
  • list.addAll()

下面我们依次展开讨论

准备工作

  • 创建2个实体类:Student(学生)、Class(班级)

Student:

@Data
@RequiredArgsConstructor
@AllArgsConstructor
public class Student {
    private String name; //姓名
    private Class theClass; //班级
}

Class:

@Data
@RequiredArgsConstructor
@AllArgsConstructor
public class Class {
    private String className; //班级名称
    private Integer classMembers; //班级人数
}
  • 便于测试,封装打印公用方法
 //打印方法
 static void print(List<Student> source,List<Student> copy){
       //原集合
       for (Student s: source){
           System.out.println(s);
       }
       System.out.println("-----------------------------华丽的分割线-------------------------------");
       //复制集合
       for (Student s: copy){
           System.out.println(s);
       }
   }
  • 准备数据
public static void main(String[] args) {
    //创建2个班级
    Class class1 = new Class("1班", 40);
    Class class2 = new Class("2班", 30);
    //创建4个学生
    Student student1 = new Student("学生1",class1);
    Student student2 = new Student("学生2",class1);
    Student student3 = new Student("学生3",class2);
    Student student4 = new Student("学生4",class2);
    //学生加入集合中
    List<Student> students = new ArrayList<>();
    students.add(student1);
    students.add(student2);
    students.add(student3);
    students.add(student4);
  
}

第一种方式:new ArrayList()
    //复制集合
    List<Student> copyStudents = new ArrayList<>(students);

查看修改前原集合和复制集合:

1.png 遍历输出查看原集合和复制集合:

      
  //对复制的集合进行修改,修改班级为2班
  for (Student s: copyStudents){
      s.setTheClass(class2);
  }

查看结果:

2.png

可以看出对复制集合修改的时候原集合也被修改了,这其实是浅拷贝,关于深拷贝和浅拷贝在讨论完集合的集中拷贝方法之后我们再讨论。

第二种方式:Object.clone()

使用list的clone()方法,这里注意只能使用ArrayList而不能使用List,因为在Object中clone()方法使用protected方法修饰,ArrayList重写的clone()方法并且是public修饰

3.png

//学生加入集合中
ArrayList<Student> students = new ArrayList<>();
students.add(student1);
students.add(student2);
students.add(student3);
students.add(student4);

//新建集合
ArrayList<Student> copyStudents = (ArrayList<Student>) students.clone();

打印原集合和复制集合

4.png

第三种方式:BeanUtils.copyProperties()

使用org.springframework.beans包下的BeanUtils拷贝

遍历students集合,取出其中的元素然后调用 BeanUtils.copyProperties();

   for (Student s: students){
        Student newStudent = new Student();
        BeanUtils.copyProperties(s, newStudent);
        copyStudents.add(newStudent);
    }

上面这段代码需要BeanUtils相关依赖,于是我懒得添加相关依赖,直接手动get、set

 //复制
  for (Student s: students){
      Student newStudent = new Student();
      newStudent.setName(s.getName());
      newStudent.setTheClass(s.getTheClass());
      copyStudents.add(newStudent);
  }
  //修改复制数组
  for (Student s: copyStudents){
      s.setTheClass(class2);
  }

e86dcbcc07763a13ad3dfc0e897f473a.png

可以看到修改复制集合的theClass并没有修改原集合的值。但是,注意我这里是修改的Student的属性theClass对象,当我修改的是student的属性theClass的属性时,原集合的也会被修改。

for (Student s: copyStudents){
    s.getTheClass().setClassName("3");
}

查看结果:

08eab3451eae97461b71c7e08f6b1d89.png

看到这里,相信大家知道我最上面的代码为啥要new两次对象了吧。

第四种方式:Collections.copy()

Collections.copy(target,source)有几个注意事项

1.目标集合在前,源集合在后

2.目标集合元素大小必须大于或等于源集合元素个数,否则会异常

在使用这个方法时,可以先将目标集合加入和原集合元素个数相同数量的空值

//目标集合
ArrayList<Student> copyStudents = new ArrayList<>(Arrays.asList(new Student[students.size()]));

然后调用打印方法,查看结果:

Collections.copy(copyStudents,students);

93e18b650fb6995672dca9d0c104e6c9.png

修改复制的集合

for (Student s: copyStudents){
   s.getTheClass().setClassName("3");
}

结果也是修改复制集合影响原集合

48fedd7465af4cc7c0c6c2f0062b29e2.png

第五种方式:list.addAll()

addAll,顾名思义就是将所有元素加入到集合中,最后调用了System.arraycopy()方法

80457fd60c79146bad37aacc319cc1d2.png

测试修改:结果也是一样引用传递

0d4e88c382ed221191a8168596a5808a.png

上面介绍的只是部分方法,还有很多实现方式这里就不讨论了(我也不会)

最后,我使用了fastjson提供的JSONArry的方法进行了集合复制

List<InsightOutputDiffSumMain> list = new ArrayList<>();
//拷贝集合
JSONArray jsonArray = (JSONArray) JSONArray.toJSON(incomeList);
List sumMains = JSONArray.toJavaObject(jsonArray, List.class);
for (Object main : sumMains) {
    list.add(JSONArray.toJavaObject((JSONObject) main, InsightOutputDiffSumMain.class));
}

在我写这里的时候我又学到了另一种类似的方式,感觉更清晰

List<InsightOutputDiffSumMain> list = new ArrayList<>();
JSONArray jsonArray = new JSONArray();
jsonArray.addAll(incomeList);
String jsonString = jsonArray.toJSONString();
list = JSON.parseArray(jsonString,InsightOutputDiffSumMain.class);

我的理解(当然还有参考资料),浅拷贝是拷贝对象的引用地址,复制的对象和原对象共享同一堆内存。所以修改其中一个对象另一个也会跟着改变。深拷贝是复制对象,开辟新的空间,创建和原对象一摸一样的对象,不和原对象共享内存,修改复制对象不会对原对象产生影响。


写在最后: 在以后的学习工作中,希望自己能够坚持写文章,和大家探讨,如有错误之处,请大家指出,希望能和大家一起成长。