可能很多人都知道java中参数有形参和实参之分,却不知道区别到底是什么;知道Java中内存分为栈、堆、方法区等5片内存,不知道每片内存中保存的都是什么;关于参数的传递到底是值传递还是引用传递傻傻分不清楚。本文将为你逐一揭秘! 一、形参和实参:public static void test(String name){
System.out.println(name);
}
test方法中的参数name就是形参,只有在test方法在被调用这个name的生命周期才开始,才会分配内存空间,当test方法调用完后,这个name也就不复存在。 public static void main(String[] args){
String name = "刘亦菲";
test(name);
}
这个String name = "刘亦菲"中这个name,在test方法被调用之前就就已被创建并且初始化,在调用test方法时,它就被当作实际参数传入,这就是实参。 二、Java中的内存:Java中内存分为5片,分别是栈、堆、方法区、程序计数器、本地方法栈。 1、栈:又称虚拟机栈。特点是先进后出。栈的线程是私有的,也就是线程之间的栈是隔离的。栈中有若干栈帧,每个栈帧对应一个方法。也就是说,当程序开始执行一个方法时,就会在栈中创建一个栈帧入栈,方法结束后,该栈帧出栈。看下面的图解: 每个栈帧主要包括: 2、堆:堆内存用来存储对象和数组。数组以及所有new出来的对象都存储在堆内存中。在JVM中只有一个堆,所以堆是被所有线程共享的。 3、方法区:方法区也是所有线程共享的区域,主要存储静态变量、常量池等。 三、数据在内存中的存储:1、基本类型的存储: int age = 6;
int grade = 6;
int weight = 50;
先创建一个age变量,存储在栈帧中的局部变量表,然后查找栈中是否有字面量值为6的内容,如果有,直接把age指向这个地址,没有开辟内存空间来存储"6"这个内容,同时让age指向它。当创建grade变量时,因为已经有字面量为"6"的内容了,所以直接拿过来用。所以栈中的数据在当前线程下是共享的。上面的代码在内存中的图解如下: 如果给age重新赋值: age = 10;
难么就会在栈中查找是否有字面量为"10"的内容,有就直接拿来用,没有就开辟内存空间存储"10",然后age指向这个10。所以基本类型的变量,变量值本身是不会改变的,重新赋值后,只是指向了新的引用而已。 public class User{
private int age;
private String name;
private int grade;
......
}
调用: User user = new User();
在内存中的存储图解: 2、引用类型的存储:通过上图可以发现,执行 User user = new User();
时分两个过程: User user;// 定义变量
user = new User();// 赋值
定义变量时,会在栈中开辟内存空间存放user变量;赋值时会在堆内存中开辟内存空间存储User实例,这个实例会有一个地址值,同时把这地址值赋给栈中的user变量。所以引用类型的变量名存储在栈中,变量值存储的是堆中相对应的地址值,并不是存储的实际内容。 四、参数传递问题:关于参数的传递,可能有点难理解,到底是值传递还是引用传递?下面一起来学习一下: public class Test {
public static void test(int age,String name){
System.out.println("传入的name:"+name);
System.out.println("传入的age:"+age);
age = 66;
name = "张馨予";
System.out.println("方法内重新赋值的name:"+name);
System.out.println("方法内重新赋值的age:"+age);
}
public static void main(String[] args){
String name = "刘亦菲";
int age = 44;
test(age,name);//调用方法
System.out.println("方法执行后的name:"+name);
System.out.println("方法执行后的age:"+age);
}
}
执行结果如下: 从结果可以发现,name和age在方法调用后并没有改变,所以传入方法的只是实参的拷贝。 @Data
public class User {
private String name;
private int age;
}
测试: public class Test {
public static void userTest(User user){
System.out.println("传入的user:"+user);
user.setName("张馨予");
user.setAge(20);
System.out.println("方法内重新赋值的user:"+user);
}
public static void main(String[] args){
User user = new User();
user.setName("刘亦菲");
user.setAge(18);
userTest(user);//调用方法
System.out.println("方法执行后的user:"+user);
}
}
结果如下: 
可以看到在方法内对user重新赋值,直接影响这个对象,所以方法执行完毕后输出的是修改后的user。 对上面的测试方法稍作修改: public class Test {
public static void userTest(User user){
System.out.println("传入的user:"+user);
user = new User();//新增这行代码
user.setName("张馨予");
user.setAge(20);
System.out.println("方法内重新赋值的user:"+user);
}
public static void main(String[] args){
User user = new User();
user.setName("刘亦菲");
user.setAge(18);
userTest(user);//调用方法
System.out.println("方法执行后的user:"+user);
}
}
执行结果如下: 
结果却是,方法执行后的user竟然没改变。 分析一下这两次的执行过程:第一次: 第一次执行过程如上图,main方法进栈后,在堆中new了一个user对象x0001,然后调用userTest方法,userTest方法进栈,并且把user对象的地址值x0001传入userTest方法,所以在userTest方法中对user进行操作直接影响地址值为x0001的对象。所以就出现了第一次运行结果。 第二次: 第二次执行过程如上图,main方法进栈后,在堆中new了一个user对象x0001,然后调用userTest方法,userTest方法进栈,并且把user对象的地址值x0001传入userTest方法,在此之前都是和第一次一样的。接下来在该方法中有 user = new User();
到了这里,又在堆中new了一个user对象x0002,然后让栈中user变量指向新的user对象的地址值x0002。所以接下来在方法中对user的操作都是对地址值为x0002的对象的操作,自然不会影响到地址值为x0001的对象。所以就出现了第二次的运行结果。 小结:由上面的案例可以得出结论,基本类型传的是值的副本,引用类型传的是地址值,所以不论传的是引用类型还是基本类型,其实都是值传递。
|