目录
起因
前两天一道题,看排名第一的题解把我难到了,当时百度好久没找到问题原因。今天突然思如泉涌,顺利解之。
原题目是LeetCode 200. 岛屿数量,解题思路也很简单,遇见一个岛屿“1”,就上下左右遍历把相连的“1”都变成“0”,然后把改变后的数组返回替换掉原数组,代码如下。
class Solution {
public int numIslands(char[][] grid) {
// dfs
int m = grid.length,n = grid[0].length;
int num = 0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j]=='1'){
num++;
grid = dfs(grid,i,j);
}
}
}
return num;
}
// (i,j) 上下左右往外扩散全变成 0
public char[][] dfs(char[][] grid,int i,int j){
int m = grid.length,n = grid[0].length;
grid[i][j]=0;
// 往左
if (j>=1 && grid[i][j-1]=='1'){
grid = dfs(grid,i,j-1);
}
// 往右
if (j<n-1 && grid[i][j+1]=='1'){
grid = dfs(grid,i,j+1);
}
// 往上
if (i>=1 && grid[i-1][j]=='1'){
grid = dfs(grid,i-1,j);
}
// 往下
if (i<m-1 && grid[i+1][j]=='1'){
grid = dfs(grid,i+1,j);
}
return grid;
}
}
但是看完题解我懵了,直接用的void 函数上下左右遍历。参数传递不是值传递吗?void返回空原函数不是不会改变吗?难道是void和数组结合有特定的效果?但是百度一通也没找到合适的答案。
class Solution {
public int numIslands(char[][] grid) {
if (grid == null || grid.length == 0) {
return 0;
}
int nr = grid.length;
int nc = grid[0].length;
int num_islands = 0;
for (int r = 0; r < nr; ++r) {
for (int c = 0; c < nc; ++c) {
if (grid[r][c] == '1') {
++num_islands;
dfs(grid, r, c);
}
}
}
return num_islands;
}
// DFS扫描整个二维网格。如果一个位置为 1,则以其为起始节点开始进行深度优先搜索。在深度优先搜索的过程中,每个搜索到的 1 都会被重新标记为 0。
void dfs(char[][] grid, int r, int c) {
int nr = grid.length;
int nc = grid[0].length;
if (r < 0 || c < 0 || r >= nr || c >= nc || grid[r][c] == '0') {
return;
}
grid[r][c] = '0';
// 上
dfs(grid, r - 1, c);
// 下
dfs(grid, r + 1, c);
// 左
dfs(grid, r, c - 1);
// 右
dfs(grid, r, c + 1);
}
}
然后今天看文章偶然看到“地址传递”,瞬间想到了这个题目。之前一直理解的“值传递”只是单纯的数值传输。其实并不一定,数组在函数传递没法传递值,只能把数组在内存中的地址作为值传递,这样一想问题就迎刃而解了。
Java 参数传递规则
Java 的参数本质上都是以值传递的形式传入方法中,而不是引用传递。但是值传递应该分两种(值传递、地址传递):
1.当参数是Java基本类型时,传递的参数是值的拷贝,原值不会随之变动。此时是单纯的数值传递
由于栈中存的就是“实际值”(java基本类型的值存储在栈中,不在堆中),所以传递的是基本类型的“实际值”的拷贝。
2.当参数是引用类型(对象、数组等)时候,传递的是对象或者数组所引用的地址,改变传递值就是改变原值。此时是对象在内存中的地址以值的方式传递到形参
由于栈中存的是地址(该地址指向堆内存中存储位置,即引用),所以传递的是“堆中的地址”的拷贝。所以我们说成是“引用(址)传递”。
注意:
1. “String和8大基本类型的包装类”是不可变类型,即特殊的引用类型。每次修改操作都会新生成一个对象并重新赋给引用,栈中的地址会不断更换,所以出现了不能修改值的效果,看起来像是值传递了。
2. 而一般对我们自己创建的类进行修改操作,就会顺着引用的地址“找到并修改掉”原来的值,所以达到了引用传递的效果。
实际测试:
第一种很好理解,比如说传递个int 5,第二种就适合撸代码测试下。
package dope;
// public class Dog {
// String name;
// Dog(String name) {
// this.name = name;
// }
// String getName() {
// return this.name;
// }
// void setName(String name) {
// this.name = name;
// }
// }
class Test{
public static void main(String[] args) {
Dog dog = new Dog("A");
System.out.println(dog.toString());
change(dog);
System.out.println(dog.getName());
}
private static void change(Dog dog) {
System.out.println(dog.toString());
dog.setName("B");
}
}
输出结果:
dope.Dog@15db9742
dope.Dog@15db9742
B
显然main函数和change函数中 dog 对象的地址没有改变,是同一个对象,name结果是 B。参数传递的是“地址值”,在change方法中改变对象的值会改变原函数中对象该值。