我们在写代码时常常遇到需要区分究竟这个操作使用的是变量的值还是变量的引用的问题,这些问题可能会对学习JAVA的新手造成不少的困扰。
一个错误判断引用和值的例子
假设我们用一个嵌套的list来表示一个全是0的矩阵,如果用下面的方法:
class WrongExample {
List<Integer> row = new ArrayList<>();
List<List<Integer>> matrix = new ArrayList<>();
//给矩阵添加一行一列
void matadd() {
row.add(0);
matrix.add(row);
if(matrix.size() > 1) {
for(int i=0; i<matrix.size()-1; i++) {
matrix.get(i).add(0);
}
}
}
}
看起来这段代码似乎是这样运行的:
第一次matadd()
row: 0 (row.add(0))
matrix: <row0> 0 (matrix.add(row))
第二次matadd()
row: 0 0 (row.add(0))
matrix: <row0> 0 (matrix.add(row))
<row1> 0 0
matrix: <row0> 0 0 (matrix.get(0).add(0))
<row1> 0 0
然而,真的是这样的吗?
只要运行一下这段代码,我们就会发现,得到的结果跟我们预期的完全不同,在运行2次matadd()之后,我们得到的矩阵,实际上是这样的:
0 0 0
0 0 0
这是为什么呢?实际上,matrix.add(row)操作向matrix中添加的是row的引用,而不是row的拷贝,也就是说,matrix这个list里的2个list都是row,而不是row0和row1,对row做的所有操作都会影响到matrix里的2个row,同样地,对matrix里的row做的所有操作都相当于对row做了操作。
那么,我们就可以很容易地看出,这个全过程实际上对row进行了3次add(0)操作,导致row最后的值为{0,0,0},而由于matrix中的两个row都是引用于同一个row,也就会产生相同的结果。
处理方法
处理方法其实很简单,只要每次都手动拷贝一个副本,再将这个副本添加到matrix中即可,具体见下方代码第8、9行。
class CorrcetExample {
List<Integer> row = new ArrayList<>();
List<List<Integer>> matrix = new ArrayList<>();
//给矩阵添加一行一列
void matadd() {
row.add(0);
List<Integer> tem = new ArrayList<>();
tem.addAll(row);
matrix.add(tem);
if(matrix.size() > 1) {
for(int i=0; i<matrix.size()-1; i++) {
matrix.get(i).add(0);
}
}
}
}
for循环中的一个例子
我们知道,for循环有2种实现方式,这里我们分别用两种方式来遍历一个类型为Integer的list。
List<Integer> test = new ArrayList<>();
test.add(1);
test.add(2);
test.add(3);
for(int i=0; i<test.size(); i++) {
test.get(i) = 0;
}
/*这里省略一段打印list内容的代码*/
for(int a : test) {
a = 1;
}
/*这里省略一段打印list内容的代码*/
我们期望的结果是:
0 0 0
1 1 1
而实际会输出的结果是:
0 0 0
0 0 0
显然,第一种循环是直接引用了test并对其进行修改,而第二种循环是对test中的每个元素都创建了一个拷贝,对拷贝进行修改,自然不会影响原来的元素。
总结
这里只是挑了我曾经犯过错的2个地方来讲,实际上需要区分值和引用的地方还有很多,在编写程序时,要多加思考,不能按照自己“以为的”来编程。