简介
本文介绍ArrayList的浅拷贝问题的原因和解决方案。
问个问题:先new ArrayList创建了list1并用add添加对象,再new ArrayList创建了list2,然后list2.addAll(list1)。此时如果list1的数据变了,那么list2的数据是否改变?
答案:会改变,因为addAll是浅拷贝(拷贝引用地址)。new ArrayList(list1)也是同样的结果。
问题复现
代码
controller
package com.knife.controller; import com.knife.entity.User; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; import java.util.List; @RestController public class HelloController { @GetMapping("/test") public String test() { List<User> userList = new ArrayList<>(); User user1 = new User(); user1.setId(1L); user1.setUserName("Tony"); User user2 = new User(); user2.setId(2L); user2.setUserName("Pepper"); userList.add(user1); userList.add(user2); System.out.println("旧列表(修改前):" + userList); List<User> newUserList = new ArrayList<>(userList); newUserList.get(0).setUserName("AA"); System.out.println("旧列表(修改后):" + userList); System.out.println("新列表(修改后):" + newUserList); return "test success"; } }
entity
package com.knife.entity; import lombok.Data; @Data public class User { private Long id; private String userName; }
测试
后端结果:(旧list的对象也被改变了)
旧列表(修改前):[User(id=1, userName=Tony), User(id=2, userName=Pepper)] 旧列表(修改后):[User(id=1, userName=AA), User(id=2, userName=Pepper)] 新列表(修改后):[User(id=1, userName=AA), User(id=2, userName=Pepper)]
原因分析
打断点分析
可以发现,旧的list和新的list,里边的user对象都是同一个引用。
代码追踪
代码的调用关系如下所示:
java.util.ArrayList#ArrayList(java.util.Collection<? extends E>) Arrays.copyOf(elementData, size, Object[].class); System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
也就是说:System.arrayCopy是浅拷贝。
解决方案
方案1:用JSON转换
说明
先将旧list转为json,再将json反序列化为新list,再修改新list的对象的值。
下边的JsonUtil代码见这里。
代码
package com.knife.controller; import com.fasterxml.jackson.core.type.TypeReference; import com.knife.entity.User; import com.knife.util.JsonUtil; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; import java.util.List; @RestController public class HelloController { @GetMapping("/test") public String test() { List<User> userList = new ArrayList<>(); User user1 = new User(); user1.setId(1L); user1.setUserName("Tony"); User user2 = new User(); user2.setId(2L); user2.setUserName("Pepper"); userList.add(user1); userList.add(user2); System.out.println("旧列表(修改前):" + userList); String json = JsonUtil.toJson(userList); List<User> newUserList = JsonUtil.toObject(json, new TypeReference<List<User>>(){}); newUserList.get(0).setUserName("AA"); System.out.println("旧列表(修改后):" + userList); System.out.println("新列表(修改后):" + newUserList); return "test success"; } }
测试(新列表改动不影响旧列表)
旧列表(修改前):[User(id=1, userName=Tony), User(id=2, userName=Pepper)] 旧列表(修改后):[User(id=1, userName=Tony), User(id=2, userName=Pepper)] 新列表(修改后):[User(id=1, userName=AA), User(id=2, userName=Pepper)]
方案2:新建内部对象
说明
新建对象,将原来列表的每一个对象的属性拷贝进去。
下边BeanUtil的代码见这里。
代码
package com.knife.controller; import com.knife.entity.User; import com.knife.util.BeanUtil; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; import java.util.List; @RestController public class HelloController { @GetMapping("/test") public String test() { List<User> userList = new ArrayList<>(); User user1 = new User(); user1.setId(1L); user1.setUserName("Tony"); User user2 = new User(); user2.setId(2L); user2.setUserName("Pepper"); userList.add(user1); userList.add(user2); System.out.println("旧列表(修改前):" + userList); List<User> newUserList = BeanUtil.deepCopy(userList, User.class); newUserList.get(0).setUserName("AA"); System.out.println("旧列表(修改后):" + userList); System.out.println("新列表(修改后):" + newUserList); return "test success"; } }
测试(新列表改动不影响旧列表)
旧列表(修改前):[User(id=1, userName=Tony), User(id=2, userName=Pepper)] 旧列表(修改后):[User(id=1, userName=Tony), User(id=2, userName=Pepper)] 新列表(修改后):[User(id=1, userName=AA), User(id=2, userName=Pepper)]
请先
!