为什么说java只有按值传递
学过Java基础的人都知道:值传递和引用传递是初次接触Java时的一个难点,有时候记得了语法却记不得怎么实际运用,有时候会的了运用却解释不出原理,而且坊间讨论的话题又是充满争议:有的论坛帖子说Java只有值传递,有的博客说两者皆有;这让人有点摸不着头脑。
Java到底是按值传递还是按引用传递的呢?国外的网站上关于这个问题的讨论非常之多。官方答案:The Java Spec says that everything in Java is pass-by-value. There is no such thing as “pass-by-reference” in Java. 官方的说法是在java中只有按值传递,并没有所谓的按引用传递。
错误理解
在开始深入讲解之前,有必要纠正一下大家以前的那些错误看法了。如果你有以下想法,那么你有必要好好阅读本文。
错误理解一:值传递和引用传递,区分的条件是传递的内容,如果是个值,就是值传递。如果是个引用,就是引用传递。
错误理解二:传递的参数如果是普通类型,那就是值传递,如果是对象,那就是引用传递。
实参与形参
形式参数:是在定义函数名和函数体的时候使用的参数,目的是用来接收调用该函数时传入的参数。
实际参数:在调用有参函数时,主调函数和被调函数之间有数据传递关系。在主调函数中调用一个函数时,函数名后面括号中的参数称为“实际参数”。
求职策略
我们说当进行方法调用的时候,需要把实际参数传递给形式参数,那么传递的过程中到底传递的是什么东西呢?
这其实是程序设计中**求值策略(Evaluation strategies)**的概念。
在计算机科学中,求值策略是确定编程语言中表达式的求值的一组(通常确定性的)规则。求值策略定义何时和以何种顺序求值给函数的实际参数、什么时候把它们代换入函数、和代换以何种形式发生。
值传递和引用传递
定义
- 值传递:是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
- 引用传递:是指在调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。
例子
有了上面的概念,然后大家就可以写代码实践了
于是就有了下面的代码:
1 2 3 4 5 6 7 8 9 10 11 | public static void main(String[] args) {<!-- --> Test t = new Test(); int i = 10; t.pass(i); System.out.println("主方法:"+i); } public void m(int j) {<!-- --> j = 20; System.out.println("m方法:"+j); } |
上面的代码中,我们在m方法中修改了参数j的值,然后分别在m方法和main方法中打印参数的值。输出结果如下:
1 2 | m方法:20 主方法:10 |
可见,m方法内部对j的值的修改并没有改变实际参数i的值。那么,按照上面的定义,有人得到结论:Java的方法传递是值传递。
但是,很快就有人提出质疑了。然后,他们会搬出以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 | public static void main(String[] args) { Test t = new Test(); User u = new User(); u.setName("张三"); t.m(u); System.out.println("主方法:" + u.getName()); } public void m(User user) { user.setName("李四"); System.out.println("m方法:" + user.getName()); } |
运行结果为:
1 2 | m方法:李四 主方法:李四 |
看到这两个例子,很多人可能就理解为:传递的参数如果是普通类型,那就是值传递,如果是对象,那就是引用传递。
如果你这样理解,那么你就进入了一个思维误区。我们再看一下下面这个例子:
1 2 3 4 5 6 7 8 9 10 11 12 | public static void main(String[] args) {<!-- --> Test t = new Test(); String name = "张三"; t.m(name); System.out.println("主方法:" + name); } public void m(String name) {<!-- --> name = "李四"; System.out.println("m方法:" + name); } |
运行结果为:
1 2 | m方法:李四 主方法:张三 |
这又作何解释呢?同样传递了一个对象,但是原始参数的值并没有被修改,难道传递对象又变成值传递了?
上面,我们举了三个例子,表现的结果却不一样,这也是导致很多初学者对于Java的传递类型有困惑的原因。
分析
其实,我们上面的例子中,第二个例子是存在一定问题的。
我在寻找这个问题答案的时候曾看到过一个例子,我觉得很贴切,这里我就借用一下
你有一把钥匙,当你的朋友想要去你家的时候,如果你直接把你的钥匙给他了,这就是引用传递。这种情况下,如果他对这把钥匙做了什么事情,比如他在钥匙上刻下了自己名字,那么这把钥匙还给你的时候,你自己的钥匙上也会多出他刻的名字。
你有一把钥匙,当你的朋友想要去你家的时候,你复刻了一把新钥匙给他,自己的还在自己手里,这就是值传递。这种情况下,他对这把钥匙做什么都不会影响你手里的这把钥匙。
但是,不管上面哪种情况,你的朋友拿着你给他的钥匙,进到你的家里,把你家的电视砸了。那你说你会不会受到影响?
而我们在m方法中,改变user对象的name属性的值的时候,不就是在“砸电视”么。你改变的不是那把钥匙,而是钥匙打开的房子。
所以说,第二个例子是存在一定问题的。
我们再来看下面这个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public static void main(String[] args) {<!-- --> Test t = new Test(); User u = new User(); u.setName("张三"); System.out.println("更改前:" + u); t.m(u); System.out.println("更改后:" + u); } public void m(User user) {<!-- --> user.setName("李四"); //System.out.println("m方法:" + user.getName()); } |
运行结果为:
1 2 | 更改前:utils.User@15db9742 更改后:utils.User@15db9742 |
从结果可以看出,我们传递的对象的地址是没有被改变的,说明我们的钥匙没有被更改,更改的只是钥匙对应的对象。
在判断实参内容有没有受影响的时候,要看传的的是什么,如果你传递的是个地址,那么就看这个地址的变化会不会有影响,而不是看地址指向的对象的变化。就像钥匙和房子的关系。所以说,Java中其实还是值传递的,只不过对于对象参数,值的内容是对象的引用。
其实,我们有一个简单的辨别方法,那就是我们定义里说的:
值传递是会将实际参数复制一份,而引用传递是没有复制的,所以,值传递和引用传递的区别并不是传递的内容。而是实参到底有没有被复制一份给形参。
结论
java只有按值传递