探讨Java参数传递问题

前言:

可能很多人都知道参数有形参和实参之分,却不知道区别到底是什么;知道Java中内存分为栈、堆、方法区等5片内存,不知道每片内存中保存的都是什么;关于参数的传递到底是值传递还是引用传递傻傻分不清楚。本文将为你逐一揭秘!


欢迎大家关注我的公众号 javawebkf,目前正在慢慢地将简书文章搬到公众号,以后简书和公众号文章将同步更新,且简书上的付费文章在公众号上将免费。


一、形参和实参:

  • 形参:就是定义方法时,该方法携带的参数。比如定义如下方法:
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"的内容了,所以直接拿过来用。所以栈中的数据在当前线程下是共享的。上面的代码在内存中的图解如下:


image.png

如果给age重新赋值:

age = 10;

难么就会在栈中查找是否有字面量为"10"的内容,有就直接拿来用,没有就开辟内存空间存储"10",然后age指向这个10。所以基本类型的变量,变量值本身是不会改变的,重新赋值后,只是指向了新的引用而已。


重新赋值
  • 基本类型的成员变量:基本类型的成员变量的变量名和值都是存储在堆内存中的,其生命周期和对象是一致的。看下面的代码:
public class User{
   private int age;
   private String name;
   private int grade;
   ......
}

调用:

User user = new User();

在内存中的存储图解:


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的对象。所以就出现了第二次的运行结果。

  • 小结:由上面的案例可以得出结论,基本类型传的是值的副本,引用类型传的是地址值,所以不论传的是引用类型还是基本类型,其实都是值传递。

总结:

本文介绍了形参与实参、Java中的内存以及各片内存主要存储哪些东西,最后讨论了一下参数传递问题。以上内容为个人理解,如果错误,欢迎批准指正!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容