引言

本文主要讲解Java方法传参、值传递、引用传递,其中涉及到JVM的相关知识,最近弥补了这一块,发现理解很多问题都变得豁然开朗了,知其所以然!(JVM在我其他博客中有详细辨析)
在这里插入图片描述
在刷力扣题目时,遇到这样一个问题,当我把一个变量传进dfs方法后,无论递归中如何对该变量赋值,最终都没有生效,今来探究其原因,说到底还是Java值传递、引用传递的问题:

方法传参

Java的方法传值,有基本数据类型以及引用类型两种:

1
2
int num = 10;   //基本类型
String str = "hello"; //引用类型

在这里插入图片描述

这里num是基本类型,str是引用类型,str是引用类型,对于引用类型来说,它本质上保存的是一个地址,指向了 “hello”这个字符串。

再还要来理解一下赋值操作(=)的具体含义:

基本类型的赋值,是直接对其保存的值进行修改的,而引用类型的赋值,本质上是改变了它指向的对象而已,原来指向的对象并没有改变:

在这里插入图片描述

继续看方法的调用:

在方法中传参数时,实际上是进行了赋值操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
作者:Intopass
链接:https://www.zhihu.com/question/31203609/answer/50992895
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

第一个例子:基本类型
void foo(int value) {
value = 100;
}
foo(num); // num 没有被改变

第二个例子:没有提供改变自身方法的引用类型
void foo(String text) {
text = "windows";
}
foo(str); // str 也没有被改变

第三个例子:提供了改变自身方法的引用类型
StringBuilder sb = new StringBuilder("iphone");
void foo(StringBuilder builder) {
builder.append("4");
}
foo(sb); // sb 被改变了,变成了"iphone4"。

第四个例子:提供了改变自身方法的引用类型,但是不使用,而是使用赋值运算符。
StringBuilder sb = new StringBuilder("iphone");
void foo(StringBuilder builder) {
builder = new StringBuilder("ipad");
}
foo(sb); // sb 没有被改变,还是 "iphone"。

从JVM视角来看,所有的线程共享的区域是堆、元空间;每一个线程有独占的虚拟机栈、本地方法栈、程序计数器。那么对于传进参数的的这个方法,自然会加载到当前线程的虚拟机栈中,所以呢,对于每个方法的局部变量来说,是绝对无法被其他方法,甚至其他线程的同一方法所访问到的,更不用谈修改了。

当我们在方法内部去声明变量时(int i =0, Object obj = null),仅仅是在栈(Stack)中加入了局部变量,并没有影响到堆(共享区域)。当我们外部new Object() 对象时,会在堆中开辟一段内存空间并初始化该对象实例,如果给这个对象赋值给方法内的obj局部变量时,仅仅只是把栈中的这个obj局部变量的地址指向改变了到了新new的Object而已。

总结:

  1. 如果参数是基本类型,Java方法参数传递的是基本类型值的拷贝
  2. 如果参数是引用类型,Java传递的是所引用的对象在堆中地址值的拷贝

所以如果想把某个对象传进方法,并且还想在方法内部修改它,则必须得调用这个对象自己的成员方法去修改,才能对堆上这个对象做实际的修改,如果只是赋值操作(=),那么仅仅改变的是方法内局部变量的指向而已,并没有真正改变原来的对象值。