0.前景回顾
代码内容如下:
private boolean dfs(char[][] board, int[][] k, String word, int index, int i, int j) {
if (index >= word.length()) {
return true;
}
if (i >= board.length || i < 0 || j < 0 || j >= board[0].length) {
return false;
}
// 已使用
if (k[i][j] == 1 || board[i][j] != word.charAt(index)) {
return false;
}
// 问题行
k[i][j] = 1;
return dfs(board, k, word, index + 1,i - 1, j)
|| dfs(board, k, word, index + 1,i + 1, j)
|| dfs(board, k, word, index + 1,i, j - 1)
|| dfs(board, k, word, index + 1,i, j + 1);
}
上面中所标识的问题行代码主要是想让k数组所在的地方进行标识,然后以相同的状态传递给后续调用的四次dfs方法。
但是,在实际的运行过程中发现最后一次dfs调用时k数组中的值和一开始不一样!!!!!
看见这种现象立马想到的是参数传递问题,子方法的调用将父方法中的值修改了。后面就在方法的调用上将k改为了k.clone(),发现还是不行(之前明明记得clone返回的不会影响原对象)。
1. 值传递 or 引用传递?
这两个概念在用C写代码的时候很常见,但是Java到底是属于哪种呢?
- 值传递:通俗的讲就是将变量具体的值拿来传递给方法,也可以认为形参是实参的一个副本;
- 引用传递:传递的是变量的地址,操作形参相当于操作实参一样。
拓展:形参和实参
形参:方法定义中的参数,像最上面例子中board,k,i这些都是形参;
实参:在调用方法时实际传入的。
第一个例子
@Test
public void test() {
int x = 1;
System.out.println("main: " + x);
change(x);
System.out.println("after change main: " + x);
}
public void change(int x) {
x = 10;
System.out.println("change : " + x);
}
结果:
main: 1
change : 10
after change main: 1
可以看到change方法中将参数的值修改成了10,主方法中的值并没有发生变化。
接着将参数类型改为变量:
@Test
public void test() {
User user = new User(1, "John");
System.out.println("before change : " + user);
change(user);
System.out.println("after change : " + user);
}
public void change(User user) {
// user = new User(user.id, user.name);
user.setName("April");
System.out.println("changer : " + user);
}
返回:
before change : User{id=1, name=‘John’}
changer : User{id=1, name=‘April’}
after change : User{id=1, name=‘April’}
这里的值被change给修改了,难道就是引用传递?
将上段代码中注释掉的行取消注释;
返回
before change : User{id=1, name=‘John’}
changer : User{id=1, name=‘April’}
after change : User{id=1, name=‘John’}
关键来了。。。
如果说对象用的是引用传递,那么在change方法中重新new了一个新的对象(值还是原来的),然后修改新对象的值,主方法中打印出来的对象属性应该和change方法中的一致。
但是,上面明明也可以修改原对象的值,这又是为什么?
可以这么认为,在传递参数的时候,是用原对象的副本(指向实际的地址)来传递,在函数中是可以执行修改对应实际地址中的值,所以主方法中会同样感知到修改。
但是如果将方法中的参数指向新的地址,比如new 一个新的对象或者将别的对象重新赋值给参数,这样形参和实参指向的就不是同一个地址空间,任何修改都不会引起原对象的变化。
2.clone()
具体clone的内容这里不作分析,主要看看最上面问题所反映的现象。
下面是相关两个场景的测试用例:
@Test
public void cp1() {
int[] array = new int[]{1,2,3};
System.out.println("before :" + Arrays.toString(array));
int[] cloneArray = array.clone();
cloneArray[0] = 0;
System.out.println("after :" + Arrays.toString(array));
}
返回
before :[1, 2, 3]
after :[1, 2, 3]
@Test
public void cp2() {
int[][] array = new int[][]{{1,2,3}};
System.out.println("before :" + Arrays.toString(array[0]));
int[][] cloneArray = array.clone();
cloneArray[0][0] = 0;
System.out.println("after :" + Arrays.toString(array[0]));
}
返回
before :[1, 2, 3]
after :[0, 2, 3]
上面两个例子中唯一区别的就是实际参与的对象一个是一维数组,一个是二维数组。从结果中可以看出,一维数组clone出来的对象修改不会影响到原对象,而二维数组会影响。
clone是浅拷贝的,对于多维数组而言,每个数组元素也是一个数组对象,当利用clone进行拷贝的时候是不会对数组中的对象进行拷贝,所以会影响原对象中值。对于多维数组要达到副本的效果就需要对每个数组中的元素(数组)再进行一次clone。
3. 解决思路
目标效果
- 将数组传递给子方法后,子方法调用结束,原数组中的值不变。
解决方法
- 每次传递给方法的是数组深拷贝的副本;
- 在方法中修改完数组中的值后,在方法结束之前恢复原来的值。