引言
本文主要讲解Java方法传参、值传递、引用传递,其中涉及到JVM的相关知识,最近弥补了这一块,发现理解很多问题都变得豁然开朗了,知其所以然!(JVM在我其他博客中有详细辨析)
在刷力扣题目时,遇到这样一个问题,当我把一个变量传进dfs方法后,无论递归中如何对该变量赋值,最终都没有生效,今来探究其原因,说到底还是Java值传递、引用传递的问题:
方法传参
Java的方法传值,有基本数据类型以及引用类型两种:
int num = 10; //基本类型
String str = "hello"; //引用类型
这里num是基本类型,str是引用类型,str是引用类型,对于引用类型来说,它本质上保存的是一个地址,指向了 "hello"这个字符串。
再还要来理解一下赋值操作(=)的具体含义:
基本类型的赋值,是直接对其保存的值进行修改的(比如这里的num直接可改为100),而引用类型的赋值,本质上是改变了它指向的对象而已,原来指向的对象并没有改变:
继续看方法的调用:
在方法中传参数时,实际上是进行了赋值操作
作者: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而已。
总结:
- 如果参数是基本类型,Java方法参数传递的是基本类型值的拷贝。
- 如果参数是引用类型,Java传递的是所引用的对象在堆中地址值的拷贝
所以如果想把某个对象传进方法,并且还想在方法内部修改它,则必须得调用这个对象自己的成员方法去修改,才能对堆上这个对象做实际的修改,如果只是赋值操作(=),那么仅仅改变的是方法内局部变量的指向而已,并没有真正改变原来的对象值。