【java】值传递引用传递
目录
一、概念
二、实例解释
1. 基本类型
过程分析如下:
2. 引用类型
过程分析如下:
3. String类型
1> String的值没有改变。
过程分析:
2>StringBuffer的值改变。
过程分析:
一、概念
如果传递的是句柄(变量名)的地址,就是引用传递;
如果传递的是值的地址,就是值传递。
java里,只有值传递,没有引用传递。
C--Java 值传递
c++ 引用传递
值:对象也是值,只要传的是个值,就是值传递
等号左边是句柄引用,右边是值。
int x1=10;
- 基本类型:
int a = 10; jvm会在栈中开辟一块空间存储变量a并赋值为10。
- 引用类型:
Sample s = new Sample(); JVM会在堆中开辟一块空间存储Sample对象,并在栈中开辟一块空间存储对象的引用s(存储Sample对象在堆中的地址),并将s指向堆中的Sample对象。
- 特殊类型:
String(是一种特殊的引用类型), JVM做了一些优化处理。
二、实例解释
1. 基本类型
public static void main(String[] mmm) {
int x1=10;
m1(x1);
System.out.println(x1);
}
public static void m1(int a) {
}
在m1如何操作,都无法改变x1,x2的指向,实际上传的是他的值。
过程分析如下:
1. 首先执行main方法的主线程会在栈中申请一块内存空间,然后分配给变量x1并赋值为10.
2. 然后执行m1(int a)方法是,会将变量x1复制一份,然后传入m1方法中的方法体去执行。
3. 复制的变量x1并不是原来的变量,只不过值也是10.
4. 方法结束,方法外输出打印x1的值, 因为原来的x1没有改变,所以输出的还是10.
而引用传递是可以改变指向的,因为传的是它的地址。
C++:
void m1(int &a){-- 碰到这种传参这种形式的&a,就是C++ a=90; } int main(){ int a=10; m1(a); printf("%d",a); return 0; }
返回结果10,a=10.
2. 引用类型
public static void main(String[] mmm) {
Person x2=new Person();
x2.age=100;
m1(x2);
System.out.println(x2.age);
}
public static void m1(Person b) {
b.age=10;
}
这种形式无法改变对象x2的值。因此输出的也是100.
过程分析如下:
1. JVM首先看方法区中有没有关于Person的*.class类、方法等信息,如果没有则将方法等类信息储存到方法区中,然后创建一个Person对象和一个对象的引用x2,在堆中申请一块区域储存Person对象并将堆中的Person对象与方法区的class类信息等相关联,并在栈中申请一块区域存放x2(存放Person对象在堆中的内存地址)指向堆中的Person对象,如果方法区中有该类的相关信息,则直接将堆中的对象和方法区的对象相关联。
2. 然后执行 x2.age=100; Person对象的类信息和方法等是存在方法区中的,Person对象去方法区中找到该类的相关方法然后执行,将Person的属性改为age:100。
3. 然后调用 m1() 方法时,虚拟机会复制一个引用 x2 ,然后将复制以后的 x2 也指向堆中的Person对象,虽然是两个引用(在栈中分别占用一小块内存区域,但是他们的引用地址是相同的都是指向堆中的Person对象的)。
4. 将复制后的x2传入 m1 方法中,重复2的过程,将对象的属性改为 age:10。
5. 方法结束,方法外打印x2中的变量值,因为x2和复制后的x2指向的都是堆中的同一个对象,所以复制到方法中的x2改变了Person对象,所以最后输出的值也是改变的。
注意!!
这种方式改变对象改变不了。
public static void main(String[] mmm) { Person x1=new Person(); Person x2=new Person(); m1(x1,x2); } public static void m1(Person a,Person b) { Person c=a; a=b; b=c; }
原因也是因为是值传递(复制一份),没有传地址,因此改变不了。
只能对指向的内存区域的值进行改变。
3. String类型
public class Demo {
public static void main(String[] args) {
String s="A";
StringBuilder ss=new StringBuilder("A");
add(s);
add(ss);
System.out.println("s:"+s); //A
System.out.println("ss:"+ss); //AA
}
public static void add(String s){
s+="A";
}
public static void add(StringBuilder s){
s.append("A");
}
}
1> String的值没有改变。
过程分析:
1、首先执行main方法的线程会去方法区中的运行时常量池中查看是否有常量"A",有则直接将引用s(和上面对象一样也是s也存储在栈中)指向常量池中的"A",如果没有则先在常量池中添加常量"A",并将引用s指向"A",显然我们这里常量池中还没有A,所以创建一个常量A,并让在栈中s引用指向A。
2、此时调用add方法,将s传入add方法中,虚拟机 会复制一份s,将复制的s传入add方法中,复制后的s和s是两个不同的引用(在栈中的地址不同但是值相同),都是指向常量池中的常量A。
3、这一步非常关键,进入方法中以后,A会变成AA,然后会在常量池中判断常量池中是否会有AA,显然没有,所以会将复制之后的引用s指向常量AA,结束方法。
4、方法结束后,在方法外打印s,因为s并没有改变,还是指向的是常量池中的A,所以会输出A。
2>StringBuffer的值改变。
过程分析:
StringBuffer类的对象能够被多次修改而不产生未使用的对象,StringBuffer的内部封装了对字符串操作的方法。
1、首先创建一个StringBuffer对象,在堆中申请一块区域然后存放该对象,然后在栈中存放ss引用(值为StringBuffer在堆中的地址)并指向堆中的 StringBuffer对象。
2、调用StringBuffer的add方法,虚拟机会复制一份ss然后传入到add方法中,ss和复制后的ss值一样,都是指向堆中的StringBuffer对象。
3、传入以后调用append方法将StringBuffer的值变为AA,然后调用toString方法将返回给StringBuffer对象。
4、方法结束,方法外输出ss,因为ss也指向的是StringBuffer对象,所以会输出最后toString返回的值。