兰州电商网站建设,一键生成logo免费图,湖北省工程建设信息官方网站,深圳一元网站建设公司转载自 为什么说Java中只有值传递
对于初学者来说#xff0c;要想把这个问题回答正确#xff0c;是比较难的。在第二天整理答案的时候#xff0c;我发现我竟然无法通过简单的语言把这个事情描述的很容易理解#xff0c;遗憾的是#xff0c;我也没有在网上找到哪篇文章可以…转载自 为什么说Java中只有值传递
对于初学者来说要想把这个问题回答正确是比较难的。在第二天整理答案的时候我发现我竟然无法通过简单的语言把这个事情描述的很容易理解遗憾的是我也没有在网上找到哪篇文章可以把这个事情讲解的通俗易懂。所以就有了我写这篇文章的初衷。这篇文章中我从什么是方法的实际参数和形式参数开始给你讲解为什么说Java中只有值传递。
辟谣时间
关于这个问题在StackOverflow上也引发过广泛的讨论看来很多程序员对于这个问题的理解都不尽相同甚至很多人理解的是错误的。还有的人可能知道Java中的参数传递是值传递但是说不出来为什么。
在开始深入讲解之前有必要纠正一下大家以前的那些错误看法了。如果你有以下想法那么你有必要好好阅读本文。
错误理解一值传递和引用传递区分的条件是传递的内容如果是个值就是值传递。如果是个引用就是引用传递。 错误理解二Java是引用传递。 错误理解三传递的参数如果是普通类型那就是值传递如果是对象那就是引用传递。
实参与形参
我们都知道在Java中定义方法的时候是可以定义参数的。比如Java中的main方法
public static void main(String[] args)
这里面的args就是参数。参数在程序语言中分为形式参数和实际参数。
形式参数是在定义函数名和函数体的时候使用的参数,目的是用来接收调用该函数时传入的参数。 实际参数在调用有参函数时主调函数和被调函数之间有数据传递关系。在主调函数中调用一个函数时函数名后面括号中的参数称为“实际参数”。
简单举个例子
public static void main(String[] args) {ParamTest pt new ParamTest();pt.sout(Hollis);//实际参数为 Hollis
}public void sout(String name) { //形式参数为 nameSystem.out.println(name);
}
实际参数是调用有参方法的时候真正传递的内容而形式参数是用于接收实参内容的参数。
值传递与引用传递
上面提到了当我们调用一个有参函数的时候会把实际参数传递给形式参数。但是在程序语言中这个传递过程中传递的两种情况即值传递和引用传递。我们来看下程序语言中是如何定义和区分值传递和引用传递的。
值传递pass by value是指在调用函数时将实际参数复制一份传递到函数中这样在函数中如果对参数进行修改将不会影响到实际参数。 引用传递pass by reference是指在调用函数时将实际参数的地址直接传递到函数中那么在函数中对参数所进行的修改将影响到实际参数。
有了上面的概念然后大家就可以写代码实践了来看看Java中到底是值传递还是引用传递 于是最简单的一段代码出来了
public static void main(String[] args) {ParamTest pt new ParamTest();int i 10;pt.pass(i );System.out.println(print in main , i is i);
}public void pass(int j) {j 20;System.out.println(print in pass , j is j);
}
上面的代码中我们在pass方法中修改了参数j的值然后分别在pass方法和main方法中打印参数的值。输出结果如下
print in pass , j is 20
print in main , i is 10
可见pass方法内部对name的值的修改并没有改变实际参数i的值。那么按照上面的定义有人得到结论Java的方法传递是值传递。
但是很快就有人提出质疑了哈哈所以不要轻易下结论咯。。然后他们会搬出以下代码
public static void main(String[] args) {ParamTest pt new ParamTest();User hollis new User();hollis.setName(Hollis);hollis.setGender(Male);pt.pass(hollis);System.out.println(print in main , user is hollis);
}public void pass(User user) {user.setName(hollischuang);System.out.println(print in pass , user is user);
}
同样是一个pass方法同样是在pass方法内修改参数的值。输出结果如下
print in pass , user is User{namehollischuang, genderMale}
print in main , user is User{namehollischuang, genderMale}
经过pass方法执行后实参的值竟然被改变了那按照上面的引用传递的定义实际参数的值被改变了这不就是引用传递了么。于是根据上面的两段代码有人得出一个新的结论Java的方法中在传递普通类型的时候是值传递在传递对象类型的时候是引用传递。
但是这种表述仍然是错误的。不信你看下面这个参数类型为对象的参数传递
public static void main(String[] args) {ParamTest pt new ParamTest();String name Hollis;pt.pass(name);System.out.println(print in main , name is name);
}public void pass(String name) {name hollischuang;System.out.println(print in pass , name is name);
}
上面的代码输出结果为
print in pass , name is hollischuang
print in main , name is Hollis
这又作何解释呢同样传递了一个对象但是原始参数的值并没有被修改难道传递对象又变成值传递了
Java中的值传递
上面我们举了三个例子表现的结果却不一样这也是导致很多初学者甚至很多高级程序员对于Java的传递类型有困惑的原因。
其实我想告诉大家的是上面的概念没有错只是代码的例子有问题。来我再来给大家画一下概念中的重点然后再举几个真正恰当的例子。
值传递pass by value是指在调用函数时将实际参数复制一份传递到函数中这样在函数中如果对参数进行修改将不会影响到实际参数。 引用传递pass by reference是指在调用函数时将实际参数的地址直接传递到函数中那么在函数中对参数所进行的修改将影响到实际参数。
那么我来给大家总结一下值传递和引用传递之前的区别的重点是什么。 我们上面看过的几个pass的例子中都只关注了实际参数内容是否有改变。如传递的是User对象我们试着改变他的name属性的值然后检查是否有改变。其实在实验方法上就错了当然得到的结论也就有问题了。
为什么说实验方法错了呢这里我们来举一个形象的例子。再来深入理解一下值传递和引用传递然后你就知道为啥错了。
你有一把钥匙当你的朋友想要去你家的时候如果你直接把你的钥匙给他了这就是引用传递。这种情况下如果他对这把钥匙做了什么事情比如他在钥匙上刻下了自己名字那么这把钥匙还给你的时候你自己的钥匙上也会多出他刻的名字。 你有一把钥匙当你的朋友想要去你家的时候你复刻了一把新钥匙给他自己的还在自己手里这就是值传递。这种情况下他对这把钥匙做什么都不会影响你手里的这把钥匙。 但是不管上面哪种情况你的朋友拿着你给他的钥匙进到你的家里把你家的电视砸了。那你说你会不会受到影响而我们在pass方法中改变user对象的name属性的值的时候不就是在“砸电视”么。你改变的不是那把钥匙而是钥匙打开的房子。
还拿上面的一个例子来举例我们真正的改变参数看看会发生什么
public static void main(String[] args) {ParamTest pt new ParamTest();User hollis new User();hollis.setName(Hollis);hollis.setGender(Male);pt.pass(hollis);System.out.println(print in main , user is hollis);
}public void pass(User user) {user new User();user.setName(hollischuang);user.setGender(Male);System.out.println(print in pass , user is user);
}
上面的代码中我们在pass方法中改变了user对象输出结果如下
print in pass , user is User{namehollischuang, genderMale}
print in main , user is User{nameHollis, genderMale}
我们来画一张图看一下整个过程中发生了什么然后我再告诉你为啥Java中只有值传递。 稍微解释下这张图当我们在main中创建一个User对象的时候在堆中开辟一块内存其中保存了name和gender等数据。然后hollis持有该内存的地址0x123456图1。
当尝试调用pass方法并且hollis作为实际参数传递给形式参数user的时候会把这个地址0x123456交给user这时user也指向了这个地址图2。
然后在pass方法内对参数进行修改的时候即user new User();会重新开辟一块0X456789的内存赋值给user。后面对user的任何修改都不会改变内存0X123456的内容图3。
上面这种传递是什么传递肯定不是引用传递如果是引用传递的话在执行user new User();的时候实际参数的引用也应该改为指向0X456789但是实际上并没有。
通过概念我们也能知道这里是把实际参数的引用的地址复制了一份传递给了形式参数。所以上面的参数其实是值传递把实参对象引用的地址当做值传递给了形式参数。
我们再来回顾下之前的那个“砸电视”的例子看那个例子中的传递过程发生了什么。 同样的在参数传递的过程中实际参数的地址0X1213456被拷贝给了形参只是在这个方法中并没有对形参本身进行修改而是修改的形参持有的地址中存储的内容。
所以值传递和引用传递的区别并不是传递的内容。而是实参到底有没有被复制一份给形参。在判断实参内容有没有受影响的时候要看传的的是什么如果你传递的是个地址那么就看这个地址的变化会不会有影响而不是看地址指向的对象的变化。就像钥匙和房子的关系。
那么既然这样为啥上面同样是传递对象传递的String对象和User对象的表现结果不一样呢我们在pass方法中使用name hollischuang;试着去更改name的值阴差阳错的直接改变了name的引用的地址。因为这段代码会new一个String再把引用交给name即等价于
name new String(hollischuang);
而原来的那个Hollis字符串还是由实参持有着的所以并没有修改到实际参数的值。 所以说Java中其实还是值传递的只不过对于对象参数值的内容是对象的引用。
总结
无论是值传递还是引用传递其实都是一种求值策略(Evaluation strategy)。在求值策略中还有一种叫做按共享传递(call by sharing)。其实Java中的参数传递严格意义上说应该是按共享传递。
按共享传递是指在调用函数时传递给函数的是实参的地址的拷贝如果实参在栈中则直接拷贝该值。在函数内部对参数进行操作时需要先拷贝的地址寻找到具体的值再进行操作。如果该值在栈中那么因为是直接拷贝的值所以函数内部对参数进行操作不会对外部变量产生影响。如果原来拷贝的是原值在堆中的地址那么需要先根据该地址找到堆中对应的位置再进行操作。因为传递的是地址的拷贝所以函数内对值的操作对外部变量是可见的。
简单点说Java中的传递是值传递而这个值实际上是对象的引用。
而按共享传递其实只是按值传递的一个特例罢了。所以我们可以说Java的传递是按共享传递或者说Java中的传递是值传递。