Java的值传递特性
发布日期:2021-06-29 03:44:51 浏览次数:3 分类:技术文章

本文共 2791 字,大约阅读时间需要 9 分钟。

文章目录


java函数传参的传递方式是以值传递的方式进行传递,即调用参数的时候实际上是将实际参数的值复制一份进行传递。

但是这在基本数据类型引用数据类型的函数传参中会体现出不一致的效果:

1,基本数据类型

我们都知道,函数中定义的基本数据类型实际上是存放到当前线程对应的栈帧空间中。

public static void main(String[] args) {        int x1 = 1;        int x2 = 2;        x3 = x1+x2;        System.out.println(x3);        System.out.println(x2);}

即:main方法被执行的时候,java虚拟机会创建一个栈帧,用于存储局部变量表和操作数栈等局部变量表用于存储对应 方法参数和方法内部定义的局部变量。 实际上局部变量表的内部是以slot(槽位)为最小单位, 32位虚拟机中一个Slot可以存放一个32位以内的数据类型(boolean、byte、char、short、int、float、reference和returnAddress八种)。 对于64为的长度如long,和double是以2个slot进行存储, reference类型虚拟机规范没有明确说明它的长度,但一般来说,虚拟机实现至少都应当能从此引用中直接或者间接地查找到对象在Java堆中的起始地址索引和方法区中的对象类型数据。

对于上面的操作语句:首先是将局部变量表索引 为0和1的整数压入操作数栈中,其后iadd指令从操作数栈中弹出那两个整数相加,再将结果压入操作数栈。第四条指令istore_2则从操作数栈中弹出结果,并把它存储到局部变量区索引为2的位置。

在这里插入图片描述

【图片参考至网络其他文章】
这个是针对于定义在函数体中的基本数据类型的内存分布,然后还有两种情况是:将基本数据类型定义为类的静态(非final)的成员变量,和静态final的成员变量。

对于类的基本数据类型(非final)的成员变量,实际上变量值的存储时存放到内存的方法区的类变量中。

对于类的基本数据类型final的成员变量,实际上变量值的存储时放到常量池中。

我们再来分析一下基本类型的值传递特性:

public static void main(String[] args) {        int x = 6;        testX(x);        System.out.println(x);}private static void testX(int x) {        x = 2;}

上面的输出语句很明显会输出6,因为值传递的特性实际上是会将存储为6的局部变量表的值复制一份,重新压入到操作数栈中,操作数栈会将对一个的值变更为2,然后再压入到局部变量表的新的索引位置下,因此是不相等的。

2,引用类型

对于引用类型的函数参数传递,我们会发现,实际上函数体对于引用对象的修改,是能够映射到对应对象的修改的

private static class User {        private String name;        public String getName() {            return name;        }        public void setName(String name) {            this.name = name;        }}public static void main(String[] args) {        User user = new User();        user.setName("zhoucg");        testU(user);        System.out.println(user.getName());}

对于上面的结果,我们会发现,最终输出的结果是:wl,即函数对于引用类型的修改,最终是修改到了对应的映射的对象上。

原因很简单,因为对象的内存分配是存储到java的堆空间的(堆空间是线程共享)main方法的栈帧空间中会使用一个slot去存储指向对应堆空间的对象地址,函数传递实际上是会将对应的堆空间的地址复制一份传递,因此其自然修改的是对应同一份堆空间上的对象。

3,String引用类型的特殊之处

public static void main(String[] args) {        String s7 = "zhoucg";        test(s7);        System.out.println(s7);}private static void test(String s) {        s += "wl";}

按照我们上面的分析,对于引用类型的传递,实际上会修改对应对象的本身,那么上面的代码输出的应该是:zhoucgwl 的呀,实际上,最终的输出结果还是zhoucg

实际上,**s7中对于“zhoucg”字符的存储此时是不会放到对应堆中,而是存放到了字符串常量池中,**进入函数之后,实际上是复制了一份指向常量池“zhoucg”的地址,执行函数之后,会将原先复制的执行“zhoucg”常量的地址重新变更,即会在常量池中新建一个“zhoucgwl”然后指向它,因此就是两个指针的指向了不同的地址空间。

总结:实际上,再去分析这个问题的时候,我们知道了基本数据类型和引用数据类型的内存分布之后自然就很容易的分析出来。

对于函数内部的基础数据类型:是通过栈帧中的局部变量表进行存储的,局部变量表存储对应的基本数据类型的变量值,通过栈帧局部变量表值的入栈和出栈操作到对应的操作数栈中,而后操作数栈最后的计算结果最终弹出存储回到局部变量表中,函数的值传递实际上是复制了一份局部变量表上的变量值。因此无论怎么操作,其最后压到局部变量表的索引肯定是和原来不同的。

对于函数内部的引用类型传递:函数的值传递实际上也是复制了一个局部变量表中reference中的值(对于对象而言,这个值指向的是内存堆中的对象地址)。因此,对于这个值的操作,自然和原始局部变量表那个指向的是同一个对象。
对于String类型的引用类型传递:实际上,同样它也是复制了栈帧中局部变量表指向这个字符的reference中的值(对于String字符而言,这个值指向的是内存中常量池的地址),但是唯一和引用类型不同的是,对于复制之后的reference的值操作,(x =“
zhoucgwl ”),其是在常量池中新建了一个zhoucgwl字符,然后把zhoucgwl的字符地址赋值给了复制的refernece。因此自然是不相等的。

转载地址:https://blog.csdn.net/zcswl7961/article/details/113572114 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:ReentrantLock的lock()和lockInterruptibly()方法的区别
下一篇:wait和notify的虚假唤醒(spurious wakeups)

发表评论

最新留言

路过,博主的博客真漂亮。。
[***.116.15.85]2024年04月13日 01时53分59秒