java引用传参和值传参再次分析
A:1. 基本类型数据的存储
1.1 局部变量
1. 基本类型的局部变量和数据都说存储在栈上的
2. int age =3 ;其实是分两步,int age;//定义变量 age =3;赋值
首先JVM创建一个名为age的变量,存于局部变量表中,然后去栈中查找是否存在有字面量值为3的内容,如果有就直接把age指向这个地址,如果没有,JVM会在栈中开辟一块空间来存储“3”这个内容,并且把age指向这个地址。因此我们可以知道:
3. 我们声明并初始化基本数据类型的局部变量时,
变量名以及字面量值都是存储在栈中,而且是真实的内容。
如果再声明一个int weight =3 ;按照刚才的思路,3已经在栈中存在,因此weight是直接指向这个地址的
4. 说明同一个栈中的数据可以共享
如果给age =5;也就是让age重新赋值,JVM会去栈中寻找字面量为5的内容,
发现没有,就会开辟一块内存空间存储5这个内容,并且把age指向这个地址,由此得出:
5.基本数据类型的数据本身是不会改变的,当局部变量重新赋值时,
并不是在内存中改变字面量内容,而是重新在栈中寻找已存在的相同的数据,
若栈中不存在,则重新开辟内存存新数据,
并且把要重新赋值的局部变量的引用指向新数据所在地址。
1.2 成员变量
1. 成员变量:顾名思义,就是在类体中定义的变量。如下:age name grade
public class Person{
2 private int age;
3 private String name;
4 private int grade;
5//篇幅较长,省略setter getter方法
6 static void run(){
7 System.out.println("run....");
8 };
9}
2. 当new一个Person对象的时候,的内存分配情况是:
Person p=new Person ;
p被分配到了栈中,指向堆中p的内存,是一个地址
age name grade 却被存储到了堆中为p对象开辟的一块空间中
3. 基本数据类型的成员变量名和值都存储于堆中,
其生命周期和对象的是一致的。
1.3 静态变量
1.前面提到方法区用来存储一些共享数据,
因此基本数据类型的静态变量名以及值存储于方法区的运行时常量池中,
静态变量随类加载而加载,随类消失而消失
2.方法区可存储的内容有:
类的全路径名、类的直接超类的权全限定名、类的访问修饰符、
类的类型(类或接口)、类的直接接口全限定名的有序列表、
常量池(字段,方法信息,静态变量,类型引用(class))等。
2.引用类型的数据存储
在执行Person per;时,JVM先在虚拟机栈中的变量表中开辟一块内存存放per变量,在执行per=new Person()时,JVM会创建一个Person类的实例对象并在堆中开辟一块内存存储这个实例,同时把实例的地址值赋值给per变量。因此可见:
1.对于引用数据类型的对象/数组,变量名存在栈中,
变量值存储的是对象的地址,并不是对象的实际内容。
B: 回到主题,引用传参和值传参
1. 值传递
在方法被调用时,实参通过形参把它的内容副本传入方法内部,此时形参接收到的内容是实参值的一个拷贝,因此在方法内对形参的任何操作,都仅仅是对这个副本的操作,不影响原始值的内容。
具体示例和那些变量是值传参和引用传参,详情见这篇博客 传送门
https://blog.csdn.net/qq_39455116/article/details/83303229
2. 引用传递:
”引用”也就是指向真实内容的地址值,在方法调用时,实参的地址通过方法调用被传递给相应的形参,在方法体内,形参和实参指向通愉快内存地址,对形参的操作会影响的真实内容。
package zhi;
public class Person {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public void myTest(Person person){
person.setName("马云");
System.out.println("+++++2+++++"+person.toString());
}
public void myTest2(Person person){
Person p =new Person();
p.setName("腾讯");
System.out.println("+++++4+++++"+p.toString());
}
public static void main(String[] args) {
Person person =new Person();
person.setName("张三");
person.setAge(120);
System.out.println("----1-----"+person.toString());
person.myTest(person);
System.out.println("=====3====="+person.toString());
person.myTest2(person);
System.out.println("=====5====="+person.toString());
}
}
输出结果:
----1-----Person{name='张三', age=120}
+++++2+++++Person{name='马云', age=120}
=====3=====Person{name='马云', age=120}
+++++4+++++Person{name='腾讯', age=null}
=====5=====Person{name='马云', age=120}
2.1 分析
1. 如果一个方法传入的对象没有new一个新的对象就是引用传参
new一个新的对象就不是引用传参了
C:总结
1. 因此可见:
在Java中所有的参数传递,不管基本类型还是引用类型,
都是值传递,或者说是副本传递。
只是在传递过程中:
A:如果是对基本数据类型的数据进行操作,
由于原始内容和副本都是存储实际值,并且是在不同的栈区,
因此形参的操作,不影响原始内容。
B:如果是对引用类型的数据进行操作,分两种情况,
(1)一种是形参和实参保持指向同一个对象地址,则形参的操作,
会影响实参指向的对象的内容。
(2)一种是形参被改动指向新的对象地址(如重新赋值引用),
则形参的操作,不会影响实参指向的对象的内容。
Why
1. 当运行一个main()方法的时候,会往虚拟机栈中压入一个栈帧,
即为当前栈帧(Main栈帧),用来存放main()中的局部变量表
(包括参数)、操作栈、方法出口等信息
2.而传入另一个方法中的值是在当前栈帧(main栈帧)中的
3.当执行到myTest()或者myTest2()方法时,
JVM也会往虚拟机栈栈中压入一个栈,即为myTest()栈帧或者myTest2()栈帧
这个栈帧用来存储这个方法中的局部变量信息,
所以,当前栈帧操作的数据是在当前栈帧里面,只是"值"是main栈帧的副本
4.而我们知道,同一个栈中的数据可以共享,但是不同栈中的数据不能共享
这也是为什么所有的传参都说副本传参的原因
详情联系[email protected]