1.数组[]和List深拷贝与浅拷贝的区别,以及实现浅深拷贝的方法 2.数组和(object)和List的传值和传址(拷贝),以及数组嵌套(对象里的数组)数组,list嵌套(对象)list(对象)的传值和传址(拷贝)(详细解说)
问题分析
当发现这个问题的时候,往往是开发项目过程中出现的迷之bug,才会在网上查询答案。其实这个原因主要在于,数组和list传递过程中出现拷贝给另一个数组变量或list,也就是相同数组共用一个地址,一个存储空间。
后端开发工作者遇到问题一般都是list中
先从list开始分析
list的传址也就是=(等于号)进行数组(=)赋值数组
@Test
public void test1(){
// List<List<String>> test = new ArrayList<>();
List<String> list1 = new ArrayList<>();
list1.add("a");
list1.add("b");
list1.add("c");
list1.add("d");
List<String> list2 = new ArrayList<>();
list2 = list1;
System.out.println("beforelist1:"+list1);
System.out.println("beforelist2:"+list2);
list1.set(1,"e");
System.out.println("afterlist1:"+list1);
System.out.println("afterlist2:"+list2);
}
结果是:
beforelist1:[a, b, c, d]
beforelist2:[a, b, c, d]
afterlist1:[a, e, c, d]
afterlist2:[a, e, c, d]
值得注意的是,拷贝不是单纯的把a的值复制一份然后给b,而是a和b引用内存当中同一个数组。
大家可以把内存理解成很多个房间,变量名就是门牌号。就相当于a和b两个门牌号指向同一个房间。
但是这种拷贝有种缺点,就是如果list1[i]变了,对应的list2[i]也会变。
list的for循环传值。
@Test
public void test3(){
// List<List<String>> test = new ArrayList<>();
List<String> list1 = new ArrayList<>();
list1.add("a");
list1.add("b");
list1.add("c");
list1.add("d");
List<String> list2 = new ArrayList<>();
for (int i=0;i<list1.size();i++){
list2.add(list1.get(i));
}
// list2 = list1;
// fastJson;
list2 = (List<String>) JSONObject.parse(JSONObject.toJSONString(list1));
System.out.println("beforelist1:"+list1);
System.out.println("beforelist2:"+list2);
list1.set(1,"e");
System.out.println("afterlist1:"+list1);
System.out.println("afterlist2:"+list2);
}
结果是:
beforelist1:[a, b, c, d]
beforelist2:[a, b, c, d]
afterlist1:[a, e, c, d]
afterlist2:[a, b, c, d]
一般情况下,需要声明后,根据实际的情况,给数组赋值。
如果是单独赋值就是a[i]=n;就行了。(切勿数组=数组赋值)
还可以使用上面的for循环,给数组循环赋值。
这样的好处,list1和list2非用共同地址,list1.set数据,并不会影响list2的数据。
list嵌套list,子list也是传址也就是=(等于号)进行数组(=)赋值数组
@Test
public void test4(){
List<List<String>> test = new ArrayList<>();
List<String> list1 = new ArrayList<>();
list1.add("a");
list1.add("b");
list1.add("c");
list1.add("d");
List<String> list2 = new ArrayList<>();
list2.add("1");
list2.add("2");
list2.add("3");
list2.add("4");
test.add(list1);
test.add(list2);
System.out.println("beforetest:"+test);
list2.set(0,"bad");
System.out.println("aftertest:"+test);
}
结果
beforetest:[[a, b, c, d], [1, 2, 3, 4]]
aftertest:[[a, b, c, d], [bad, 2, 3, 4]]
可以看到当更改list2的第一个元素时候,test父list的数值也发生改变,也就是说,test的元素都是传地址,当list2发生改变,test也会发生改变。
进阶示例
@Test
public void test4(){
List<List<String>> test = new ArrayList<>();
List<String> list1 = new ArrayList<>();
list1.add("a");
list1.add("b");
list1.add("c");
list1.add("d");
List<String> list2 = new ArrayList<>();
list2.add("1");
list2.add("2");
list2.add("3");
list2.add("4");
test.add(list1);
test.add(list2);
System.out.println("beforetest:"+test);
list2.set(0,"bad");
System.out.println("aftertest:"+test);
test.get(1).add("wawa");
System.out.println("afterchangetestlist2:"+list2);
}
结果
beforetest:[[a, b, c, d], [1, 2, 3, 4]]
aftertest:[[a, b, c, d], [bad, 2, 3, 4]]
afterchangetestlist2:[bad, 2, 3, 4, wawa]
可以看到当更改test父list2的数值时候,list2的数值也会发生改变,test的元素都是传地址,当list2发生改变,test也会发生改变。test发生改变的时候,list也会发生改变。
list嵌套list,子list里for循环传值
同理,循环里面循环赋值即可。切勿发生数组=数组(list.add(list)/list.set(list))的情况即可。
结果,list1和list2发生更改并不会影响test的更改,test的更改也不会影响list1和list2发生更改
父list嵌套子list,父list传址给另一父list
当List<list<String>>
for循环给另一个List<list<String>>
传值时候,容易出现值数组赋值数组
例如:
@Test
public void test5(){
List<List<String>> test = new ArrayList<>();
List<String> list1 = new ArrayList<>();
list1.add("a");
list1.add("b");
list1.add("c");
list1.add("d");
List<String> list2 = new ArrayList<>();
list2.add("1");
list2.add("2");
list2.add("3");
list2.add("4");
test.add(list1);
test.add(list2);
System.out.println("beforetest:"+test);
List<List<String>> temp = new ArrayList<>();
for (int i=0;i<test.size();i++){
temp.add(test.get(i));
}
// temp = (List<List<String>>) JSONObject.parse(JSONObject.toJSONString(test));
//这个是深克隆,数字和英文都为字符串
System.out.println("beforetemp:"+temp);
temp.get(0).set(0,"bad");
list1.set(2,"good");
System.out.println("aftertest:"+test);
System.out.println("aftertemp:"+temp);
}
结果:
beforetest:[[a, b, c, d], [1, 2, 3, 4]]
beforetemp:[[a, b, c, d], [1, 2, 3, 4]]
aftertest:[[bad, b, good, d], [1, 2, 3, 4]]
aftertemp:[[bad, b, good, d], [1, 2, 3, 4]]
我们可以看到,temp子元素赋值test的子元素,也就是list赋值list发生了传地址,也就是,test.get(0),temp.get(0)都是指向一个地址,list1的存地址空间.当更改三者list1地址里任一数值,三者数值也会发生改变.
父list嵌套子list,父list传值给另一父list
同理, temp = (List<List>) JSONObject.parse(JSONObject.toJSONString(test));
或循环里面循环赋值即可。切勿发生数组=数组(list.add(list)/list.set(list))的情况即可。
结果,list1和list2发生更改并不会影响test的更改,test的更改也不会影响list1和list2发生更改
list嵌套对象类,类中有list,for循环对象类赋值传址问题重点在这里
**容易犯错分析:**对象类赋值也是传地址,对象类有list,对象类=对象类的时候,对象类的list=对象类的list,对象类的list和另一个对象类的list共用一个list,对象类和对象类共用地址.
@Data
public class Pet {
private String petId;
private String petName;
private Integer breed;
private List<String> objectList;
}
@Test
public void test6(){
List<Pet> listPet = new ArrayList<>();
List<String> list1 = new ArrayList<>();
list1.add("a");
list1.add("b");
list1.add("c");
list1.add("d");
Pet pet = new Pet();
pet.setBreed(1);
pet.setPetId("001");
pet.setPetName("小白");
pet.setObjectList(list1);
//Pet pet1 = new Pet();
//pet1 = pet;
listPet.add(pet);
List<Pet> listPetTemp = new ArrayList<>();
for (int i=0;i<listPet.size();i++){
// listPetTemp.add(pet1); 替换 listPetTemp.add(listPet.get(i));
listPetTemp.add(listPet.get(i));
}
//listPetTemp = (List<Pet>) JSONObject.parse(JSONObject.toJSONString(listPet));
//深克隆, 全字符串 要写转对象,参考
//listPetTemp = JSONArray.parseArray(JSONObject.toJSONString(listPet),Pet.class);
System.out.println("beforelistPet:"+listPet);
System.out.println("beforelistPetTemp:"+listPetTemp);
list1.set(0,"bad");
listPetTemp.get(0).getObjectList().set(1,"listPetTemp");
listPetTemp.get(0).setPetName("临时存储点");
pet.setPetId("007");
System.out.println("afterlistPet:"+listPet);
System.out.println("afterlistPetTemp:"+listPetTemp);
}
beforelistPet:[Pet(petId=001, petName=小白, breed=1, objectList=[a, b, c, d])]
beforelistPetTemp:[Pet(petId=001, petName=小白, breed=1, objectList=[a, b, c, d])]
afterlistPet:[Pet(petId=007, petName=临时存储点, breed=1, objectList=[bad, listPetTemp, c, d])]
afterlistPetTemp:[Pet(petId=007, petName=临时存储点, breed=1, objectList=[bad, listPetTemp, c, d])]
可以看到,更改pet对象类,以及更改list1,会导致两个List的值进行更改,从而可以发现,两个list共用一个对象类地址,共用一个list地址.还可以看看我注释掉的三个地方,如果取消注释,结果一致,这就说明了,对象是传址给另一对象.和list差不多一致.
list嵌套对象类,类中有list,for循环对象类,对象类循环object.get(“name”)往另一个对象object.set(“name”)传值,能达到非地址传值
例子就不详细,把犯错地方展示出来,大家应该都知道咋去处理啦.
一般来说前端工程师,并不会在前端用list,他们更青睐于用[],例如var list = [] / let list = []
那么我用[],再来说一次,大家可以用菜鸟在线编辑器上手一波,
[]的传址也就是=(等于号)进行数组(=)赋值数组
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
</head>
<body>
<p>点击按钮获取myFunction()方法</p>
<button onclick="myFunction()">点我</button>
<p id="demo"></p>
<p id="demo1"></p>
<script>
var ages = [12,34,58,86,46];
var array = [];
array = ages;
array[0]=666
function myFunction() {
document.getElementById("demo").innerHTML = ages;
document.getElementById("demo1").innerHTML = array;
}
</script>
</body>
</html>
结果
点击按钮获取myFunction()方法
666,34,58,86,46
666,34,58,86,46
分析:
array[0]=666,从而影响赋值者ages[0]的参数,可想而知,是传地址,和list相当一致.
[]的数组传值数组(非地址)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
</head>
<body>
<p>点击按钮获取myFunction()方法</p>
<button onclick="myFunction()">点我</button>
<p id="demo"></p>
<p id="demo1"></p>
<script>
var ages = [12,34,58,86,46];
var array = [];
ages.forEach((v,k)=>{ //方法一
array[k]=v;} //方法一
); //方法一
array = JSON.parse(JSON.stringify(ages)) //方法二
array[0]=666
function myFunction() {
document.getElementById("demo").innerHTML = ages;
document.getElementById("demo1").innerHTML = array;
}
</script>
</body>
</html>
结果:
点击按钮获取myFunction()方法
12,34,58,86,46
666,34,58,86,46
这样的好处,ages和array非用共同地址,array更改数据,并不会影响ages的数据。
父[]嵌套子[],父[]forEach循环子[]到另一个父[](子地址传址)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
</head>
<body>
<p>点击按钮获取myFunction()方法</p>
<button onclick="myFunction()">点我</button>
<p id="demo"></p>
<p id="demo1"></p>
<script>
var ages = [[1,2,3],["a","b","c"]];
var array = [];
ages.forEach((v,k)=>{
array[k]=v;}
);
array[0][0] = 666
function myFunction() {
document.getElementById("demo").innerHTML = ages;
document.getElementById("demo1").innerHTML = array;
}
</script>
</body>
</html>
结果:
点击按钮获取myFunction()方法
666,2,3,a,b,c
666,2,3,a,b,c
可以看出共用ages和array共用[1,2,3][“a”,“b”,“c”] 地址块.
父[]嵌套子[],父[]forEach循环子[]再循坏元素赋值到另一个父[](子[]传值)
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
</head>
<body>
<p>点击按钮获取myFunction()方法</p>
<button onclick="myFunction()">点我</button>
<p id="demo"></p>
<p id="demo1"></p>
<script>
var ages = [[1,2,3],["a","b","c"]];
var array = [];
//方法一
ages.forEach((v,k)=>{
array[k]=[]
v.forEach((v1,k1)=>{
array[k][k1]=v1
})
}
);
//方法二
array = JSON.parse(JSON.stringify(ages))
array[0][0]="bad"
function myFunction() {
document.getElementById("demo").innerHTML = ages;
document.getElementById("demo1").innerHTML = array;
}
</script>
</body>
</html>
结果:
点击按钮获取myFunction()方法
1,2,3,a,b,c
bad,2,3,a,b,c
两层for循环,赋值(非数组/对象赋值)即可; 或 JSON.parse(JSON.stringify(ages));
{}/OBJECT的传址也就是=(等于号)进行OBJECT(=)赋值OBJECT
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
</head>
<body>
<p>点击按钮获取myFunction()方法</p>
<button onclick="myFunction()">点我</button>
<p id="demo"></p>
<p id="demo1"></p>
<script>
var ages = {'name':'dragonLi','age':11,'habit':'running'}
var array = {};
array = ages;
array.habit = "jumping"
function myFunction() {
document.getElementById("demo").innerHTML = ages.name+" "+ages.age+" "+ages.habit;
document.getElementById("demo1").innerHTML = array.name+" "+array.age+ " "+array.habit;
}
</script>
</body>
</html>
结果:
dragonLi 11 jumping
dragonLi 11 jumping
同理可得,传址
{}的数组传值数组(非地址)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
</head>
<body>
<p>点击按钮获取myFunction()方法</p>
<button onclick="myFunction()">点我</button>
<p id="demo"></p>
<p id="demo1"></p>
<script>
var ages = {'name':'dragonLi','age':11,'habit':'running'}
var array = {};
//方法一
array.name = ages.name;
array.age = ages.age;
array.habit = ages.habit;
//方法二
array = JSON.parse(JSON.stringify(ages))
array.age = 99
function myFunction() {
document.getElementById("demo").innerHTML = ages.name+" "+ages.age+" "+ages.habit;
document.getElementById("demo1").innerHTML = array.name+" "+array.age+ " "+array.habit;
}
</script>
</body>
</html>
结果:
点击按钮获取myFunction()方法
dragonLi 11 running
dragonLi 99 running
这样传值能保证对象深克隆成功,而非浅克隆传址
[{},{}]for循环{}传的是地址
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
</head>
<body>
<p>点击按钮获取myFunction()方法</p>
<button onclick="myFunction()">点我</button>
<p id="demo"></p>
<p id="demo1"></p>
<script>
var ages = [{name:'dragonLi',age:11,habit:'running'},{name:'millCat',age:4,habit:'eating'}]
var array = [];
ages.forEach((v,k)=>{
array[k] = v
})
array[0].habit= "skiping";
function myFunction() {
document.getElementById("demo").innerHTML = ages[0].name+" "+ages[0].age+" "+ages[0].habit+
" "+ages[1].name+" "+ages[1].age+" "+ages[1].habit+" ";
document.getElementById("demo1").innerHTML = array[0].name+" "+array[0].age+" "+array[0].habit+
" "+array[1].name+" "+array[1].age+" "+array[1].habit+" ";
}
</script>
</body>
</html>
结果
dragonLi 11 skiping millCat 4 eating
dragonLi 11 skiping millCat 4 eating
更改array[0].habit= “skiping”; 原对象ages也发生改变
[{},{}]for循环{}再循坏值对值属性赋值属性传的是值(深克隆)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
</head>
<body>
<p>点击按钮获取myFunction()方法</p>
<button onclick="myFunction()">点我</button>
<p id="demo"></p>
<p id="demo1"></p>
<script>
var ages = [{'name':'dragonLi','age':11,'habit':'running'},{'name':'millCat','age':4,'habit':'eating'}]
var array = [];
//方法一
ages.forEach((v,k)=>{
array[k]={}
array[k].name = v.name;
array[k].age = v.age;
array[k].habit = v.habit;
})
//方法二
array = JSON.parse(JSON.stringify(ages))
array[0].habit= "skiping";
function myFunction() {
document.getElementById("demo").innerHTML = ages[0].name+" "+ages[0].age+" "+ages[0].habit+
" "+ages[1].name+" "+ages[1].age+" "+ages[1].habit+" ";
document.getElementById("demo1").innerHTML = array[0].name+" "+array[0].age+" "+array[0].habit+
" "+array[1].name+" "+array[1].age+" "+array[1].habit+" ";
}
</script>
</body>
</html>
结果:
dragonLi 11 running millCat 4 eating
dragonLi 11 skiping millCat 4 eating
array[0].habit= “skiping”;不影响原对象数组
[]{}混合传地址
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
</head>
<body>
<p>点击按钮获取数组中大于 18 的所有元素。</p>
<button onclick="myFunction()">点我</button>
<p id="demo"></p>
<p id="demo1"></p>
<p id="demo2"></p>
<p id="demo3"></p>
<p id="demo4"></p>
<p id="demo5"></p>
<script>
var ages = [
{
name:'新建/模板',
roleCode:['user'],
child:[
{'name':'新建文件夹', 'page':'/createFolder','roleCode':[]},
{'name':'新建模板', 'page':'/createTemplate','roleCode':['user']},
]
},
{
name:'设置',
roleCode:[],
child:[
{'name':'用户设置','page':'/usersManage','roleCode':[]},
{'name':'权限配置','page':'/JurisdictionSet','roleCode':[]},
{'name':'导出文件清单','page':'/exportFileList','roleCode':[]},
]
},
]
let age3 =[];
function myFunction() {
ages.forEach((v,k)=>{
age3[k] = v
}
);
document.getElementById("demo").innerHTML = ages[0].child[0].name;
document.getElementById("demo1").innerHTML = age3[0].child[0].name;
age3[0].child[0].name="bad";
document.getElementById("demo4").innerHTML = ages[0].child[0].name;
document.getElementById("demo5").innerHTML = age3[0].child[0].name;
}
</script>
</body>
</html>
结果:
新建文件夹
新建文件夹
bad
bad
通过对象赋值对象传[],更改对象里的属性的child的数组里的数据,原[]和旧[]一起更改.
### []{}混合循环传值
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
</head>
<body>
<p>点击按钮获取数组中大于 18 的所有元素。</p>
<button onclick="myFunction()">点我</button>
<p id="demo"></p>
<p id="demo1"></p>
<p id="demo2"></p>
<p id="demo3"></p>
<p id="demo4"></p>
<p id="demo5"></p>
<script>
var ages = [
{
name:'新建/模板',
roleCode:['user'],
child:[
{'name':'新建文件夹', 'page':'/createFolder','roleCode':[]},
{'name':'新建模板', 'page':'/createTemplate','roleCode':['user']},
]
},
{
name:'设置',
roleCode:[],
child:[
{'name':'用户设置','page':'/usersManage','roleCode':[]},
{'name':'权限配置','page':'/JurisdictionSet','roleCode':[]},
{'name':'导出文件清单','page':'/exportFileList','roleCode':[]},
]
},
]
let age3 =[];
//方法一 适合微调
ages.forEach((v,k)=>{
age3[k] = {};
age3[k].name = v.name;
age3[k].roleCode = v.roleCode;
age3[k].child = []
v.child.forEach((v1,k1)=>{
age3[k].child[k1] = {}
age3[k].child[k1].name = v1.name;
age3[k].child[k1].page = v1.page;
age3[k].child[k1].roleCode = v1.roleCode;
})
});
//方法二
age3 = JSON.parse(JSON.stringify(ages))
function myFunction() {
document.getElementById("demo").innerHTML = ages[0].child[0].name;
document.getElementById("demo1").innerHTML = age3[0].child[0].name;
age3[0].child[0].name="bad";
document.getElementById("demo4").innerHTML = ages[0].child[0].name;
document.getElementById("demo5").innerHTML = age3[0].child[0].name;
}
</script>
</body>
</html>
一层一层让最小单位传值是基本类型,就可以保证,非地址传值. 或者JSON.parse(JSON.stringify(ages))
总结:当基本数据类型(Boolean,byte,char,String,int,Long,float,double)作为赋值传递时,传递的是实参值的副本,即传的是值,是深拷贝,是两个存储位置. 当非基本数据类型进行赋值[]=[],object=object,list.set(list1),list = list,诸如此类都是传地址.想要复制一个非基本类型,循坏到基本类型赋值就好了