ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

Java中的参数传递,到底是值传递还是引用传递?

2022-08-21 20:00:19  阅读:195  来源: 互联网

标签:参数传递 Java 传递 地址 参数 引用 pass


1、Java中的参数传递,到底是值传递还是引用传递?

结论:Java只有值传递,没有引用传递!

错误理解一:值传递和引用传递,区分的条件是传递的内容,如果是个值,就是值传递。如果是个引用,就是引用传递。
错误理解二:Java是引用传递。
错误理解三:传递的参数如果是普通类型,那就是值传递,如果是对象,那就是引用传递。

2、实参与形参
我们都知道,在Java中定义方法的时候是可以定义参数的。比如Java中的main方法,public static void main(String[] args),这里面的args就是参数。参数在程序语言中分为形式参数和实际参数。

形式参数:是在定义函数名和函数体的时候使用的参数,目的是用来接收调用该函数时传入的参数。
实际参数:在调用有参函数时,主调函数和被调函数之间有数据传递关系。在主调函数中调用一个函数时,函数名后面括号中的参数称为“实际参数”。

简单举个例子:
在这里插入图片描述
实际参数是调用有参方法的时候真正传递的内容,而形式参数是用于接收实参内容的参数。

3、基本类型与引用类型

int num = 10;
String str = "hello";
  • 1
  • 2

在这里插入图片描述
如图所示,num 是基本类型,值就直接保存在变量中。而 str是引用类型,变量中保存的只是实际对象的地址。一般称这种变量为 “引用”,引用指向实际对象,实际对象中保存着内容。

4、赋值运算符“=”的作用

num = 20;
str = "java";
  • 1
  • 2

在这里插入图片描述
对于基本类型 num,赋值运算符会直接改变变量的值,原来的值被覆盖掉。
对于引用类型 str,赋值运算符会改变引用中所保存的地址,原来的地址被覆盖掉。但是原来的对象不会被改变(重要)。
如上图所示,“hello” 字符串对象没有被改变。(没有被任何引用所指向的对象是垃圾,会被垃圾回收器GC回收)

5、值传递与引用传递
上面提到了,当我们调用一个有参函数的时候,会把实际参数传递给形式参数。但是,在程序语言中,这个传递过程中传递的两种情况,即值传递和引用传递。我们来看下程序语言中是如何定义和区分值传递和引用传递的

值传递(pass by value)是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
引用传递(pass by reference)是指在调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。

  • 值传递:将参数复制一份,修改形参不会对实参造成影响
  • 引用传递:将实参的地址传递给形参,修改形参也就是在修改实参

我们来测试几段代码:
在这里插入图片描述
上面的代码中,我们在 pass 方法中修改了参数 j 的值,然后分别在 pass 方法和 main 方法中打印参数的值。输出结果如下:

print in pass , j is 20
print in main , i is 10
  • 1
  • 2

可见,pass 方法内部对 name 的值的修改并没有改变实际参数 i 的值。那么,按照上面的定义,有人得到结论:Java 的方法传递是值传递。

但是,很快就有人提出质疑了(哈哈,所以,不要轻易下结论咯。)。然后,他们会给出以下代码:
在这里插入图片描述
同样是一个 pass 方法,同样是在 pass 方法内修改参数的值。输出结果如下:

print in pass , User{name='Tom', sex='man'}
print in main , User{name='Tom', sex='man'}
  • 1
  • 2

经过 pass 方法执行后,实参的值竟然被改变了,那按照上面的引用传递的定义,实际参数的值被改变了,这不就是引用传递了么。于是,根据上面的两段代码,有人得出一个新的结论:Java 的方法中,在传递普通类型的时候是值传递,在传递对象类型的时候是引用传递。但是,这种表述仍然是错误的。不信你看下面这个参数类型为引用类型的参数传递:
在这里插入图片描述

print in pass , Tom
print in main , Mr.Q
  • 1
  • 2

那么,问题来了。String是引用类型,new String(“Mr.Q”)在堆上创建了对象,name指向了Mr.Q的引用。那按照上面来说,应该是引用传递了,输出的结果应该pass和main是相同的,可是,为什么会不同呢?
这又作何解释呢?同样传递了一个对象,但是原始参数的值并没有被修改,难道传递对象又变成值传递了?
其实,是传递的地址值发生了改变
在这里插入图片描述
String类型在值传递和引用传递问题中比较特殊,为什么说特殊呢,因为对于一些常量字符串的创建,只要判断对象在堆中不存在,便会创建一个新的,如果是创建新对象,那么引用地址都会变。我们可以通过一个简单的例子来解释下:
在这里插入图片描述

a是:hello --- b是:你好
  • 1

String a = “hello”; 在 String 池中检查并创建一个常量:“hello”,给 a 分配一个栈内存,在此存储常量 hello 的地址。
String b= a; 给 b 分配一个栈内存,在此存储常量 hello 的地址。相当于 a 把自己持有的地址,复制给了 b。
在这里插入图片描述
b = “你好”; 在 String 池中检查是否有 “你好” 的常量。

  • 如果有,将 b 的地址指向 “你好” 的地址。
  • 如果 String 池中没有 “你好” 常量,在堆内存中创建 “你好” 常量,并将 b 地址指向 “你好”。
    在这里插入图片描述
    我们再来看一个反例,来验证 “Java中参数传递 没有引用传递”
public class Test {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Student s1 = new Student("小张");
        Student s2 = new Student("小李");
        Test.swap(s1, s2);
        System.out.println("s1:" + s1.getName());
        System.out.println("s2:" + s2.getName());
    }

    public static void swap(Student x, Student y) {
        Student temp = x;
        x = y;
        y = temp;
        System.out.println("x:" + x.getName());
        System.out.println("y:" + y.getName());
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

结果:

x:小李
y:小张
s1:小张
s2:小李

方法并没有改变存储在变量 s1 和 s2 中的对象引用。swap 方法的参数 x 和 y 被初始化为两个对象引用的拷贝,这个方法交换的是这两个拷贝。

6、Java中的值传递
值传递和引用传递之前的区别到底是什么?
在这里插入图片描述
两者的最主要区别就是是直接传递的,还是传递的是一个副本
这里我们来举一个形象的例子。再来深入理解一下传值调用和传引用调用:

  • 你有一把钥匙,当你的朋友想要去你家的时候,如果你直接把你的钥匙给他了,这就是引用传递。
  • 这种情况下,如果他对这把钥匙做了什么事情,比如他在钥匙上刻下了自己名字,那么这把钥匙还给你的时候,你自己的钥匙上也会多出他刻的名字。
  • 你有一把钥匙,当你的朋友想要去你家的时候,你复刻了一把新钥匙给他,自己的还在自己手里,这就是值传递。
  • 这种情况下,他对这把钥匙做什么都不会影响你手里的这把钥匙。

那我们再说回到这段代码中:
在这里插入图片描述

print in pass , User{name='Tom', sex='man'}
print in main , User{name='Tom', sex='man'}
  • 1
  • 2

看看在调用中,到底发生了什么?
在这里插入图片描述
在参数传递的过程中,实际参数的地址0x666被拷贝给了形参。这个过程其实就是值传递(这个值,理解为引用的地址),只不过传递的值得内容是对象的应用。

那为什么我们改了user中的属性的值,却对原来的user产生了影响呢?

其实,这个过程就好像是:你复制了一把你家里的钥匙给到你的朋友,他拿到钥匙以后,并没有在这把钥匙上做任何改动,而是通过钥匙打开了你家里的房门,进到屋里,把你家的电视给砸了。

这个过程,对你手里的钥匙来说,是没有影响的,但是你的钥匙对应的房子里面的内容却是被人改动了。

也就是说,Java对象的传递,是通过复制的方式把引用关系传递了,如果我们没有改引用关系,而是找到引用的地址,把里面的内容改了,是会对调用方有影响的,因为大家指向的是同一个共享对象。

那么,如果我们改动一下pass方法的内容:
在这里插入图片描述
上面的代码中,我们在pass方法中,重新new了一个user对象,并改变了他的值,输出结果如下:

print in pass , User{name='Tom'}
print in main , User{name='Mr.Q'}
  • 1
  • 2

也就是说,我把我的钥匙复制给了我的朋友,但是我立马换了我家的锁。因为一new就会在堆上开辟新空间,地址就发生了改变,此时的user不再指向0x666了,理解为我换锁了,朋友当然进不了我家,砸不了电视了。所以此时在pass方法中修改name,不会对我家造成任何影响。
在这里插入图片描述
上面这种传递是什么传递?肯定不是引用传递,如果是引用传递的话,在user = new User()的时候,实际参数的引用也应该改为指向0x999,但是实际上并没有。

通过概念我们也能知道,这里是把实际参数的引用的地址复制了一份,传递给了形式参数。所以,上面的参数其实是值传递,把实参对象引用的地址当做值传递给了形式参数。

所以,值传递和引用传递的区别并不是传递的内容,而是实参到底有没有被复制一份给形参。

在判断实参内容有没有受影响的时候,要看传的的是什么,如果你传递的是个地址,那么就看这个地址的变化会不会有影响,而不是看地址指向的对象的变化。

所以说,Java 中其实还是值传递,只不过对于对象参数,值的内容是对象的引用。

总结
无论是值传递还是引用传递,其实都是一种求值策略 ()。在求值策略中,还有一种叫做按共享传递 (call by sharing)。其实 Java 中的参数传递严格意义上说应该是按共享传递。

按共享传递,是指在调用函数时,传递给函数的是实参的地址的拷贝(如果实参在栈中,则直接拷贝该值)。

在函数内部对参数进行操作时,需要先拷贝的地址寻找到具体的值,再进行操作。

如果该值在栈中,那么因为是直接拷贝的值,所以函数内部对参数进行操作不会对外部变量产生影响。

如果原来拷贝的是原值在堆中的地址,那么需要先根据该地址找到堆中对应的位置,再进行操作。因为传递的是地址的拷贝,所以函数内对值的操作对外部变量是可见的。

简单点说,Java 中的传递,是值传递,而这个值,实际上是对象的引用。

  • 传递的值在栈中,直接拷贝一份值传递,改变的形参不会对实参造成影响
  • 传递的值在栈中存放的是地址(引用),先根据栈中的地址找到在堆上的值,然后把地址拷贝一份(拷贝的地址是一个值),此时形参和实参指向堆上同一个地址,形参的修改导致了实参的改变。

在这里插入图片描述

标签:参数传递,Java,传递,地址,参数,引用,pass
来源: https://www.cnblogs.com/Sweetp/p/16610689.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有