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,诸如此类都是传地址.想要复制一个非基本类型,循坏到基本类型赋值就好了