Java的按值传递 博客分类: Java java按值传递
程序员文章站
2024-03-13 14:37:57
...
问题:
最近在看Martin Fowler的《重构》一书,书中在讲临时变量的时候提到,编程的时候尽量不要去改变入参的值,因为这样的当时开发者来说是比较能理解的,但是对于后续维护者来说,这个就会比较头大。因为有时候我们根本就搞不明白为什么进入的时候是这样的,出来的为什么不是我要的值呢。因此, Martin Fowler建议如果要对入参做改变,可以定义一个返回值,然后把这个返回值重新复制给一个新的变量。
e.g.
虽然,代码多了一些,但是对于后续的维护是有大大的好处的,毕竟程序是写给人看的。
为了不改变入参的值,Martin Fowler还建议要给形参加上final修饰符,这样这个变量就不会被强制复制了。
说到这里的时候,提到了java开发经常会出错的一个点就是:java 的值传递问题。
名词解释:
值传递:java程序中,调用者调用被调用者的时候,通常都是把调用者的参数的值“拷贝”一份来进行操作的。Java里面所有的传递都是按值传递
简单说就是上面的main方法在调用changeUser方法的时候,会进行user=user1的赋值操作。也就是说,实际上我们在changeUser里做的任何改动都是对拷贝出来的副本进行的改动,不会对原来的值user1造成任何影响。那么上面的代码为什么user的值被改变了呢? 我们这里的原因有两个,一个是我们在main方法里对user重新进行了赋值。 第二个我想说的是,这里即使没有赋值也就是代码变成这样:
output也还是这样的
下面就是解释一下原因:
例子1:基本数据类型:
也就是说我们期望的想把5,放大3倍的效果并没有出来。为什么,因为在triple方法体内,我们是对另外一个副本进行了操作,但是在方法体外,x还是原来的那个值,除非,你对它重新赋值。
例如这样:
例子2:引用类型(对象,数组):
对象:
你会发现username被修改了,你很知道,为什么,不是说只对副本进行操作么,这里的值为什么会被修改了呢。
那就让我来解释一下吧。 我们都知道对象是引用类型,那么引用类型,它的值是什么呢,它总有一个值吧,不然这个东西不可能凭空存在的啊。对的,引用对象它说存的值是一个对象实例在堆内存中的地址,也就是类似:0x0000F000的值,它也是一个值,只不过它指向了另一个内存区域。这就是为什么说java中只有按值传递的原因了。
我们这个例子中,我们调用rename方法,这时我们把user的引用地址copy了一份,但是,当我们执行.操作的时候,我们是去修改的这个引用所指向的真实内存空间里的值。 就好比我在淘宝上买了个电视,我把我家的地址copy一份给快递员,快递员拿到我家的地址之后,就往我家里送电视,等电视送到了,我家里就多了一个电视的。而不是别人的家里多了电视。
同样的,如果我改动代码如下,这里先把final去掉:
output就变成这样了。
原因就是,user这个地址指向了其他人的地址,也就是我给了一个其他人的地址给快递员,东西并没有送到我家里来,那么我家里还是原来的样子,东西不会多也不会少。
数组也是一样的道理, 如下:
output:
这个值会被改变就等同于,user的真实内存内容被修改了。
例子3:final修饰
当用final修饰时,这时的形参是不能被改变的,也就是这里那个注释掉的代码是不能被执行的,原因就是地址是不能再被重新赋值的,而setter能执行就说明,当我们执行setter的时候,并没有对这个地址值本身进行操作,而是对这个地址所指向的堆内存进行了操作。
不知道各位有没有跟理解一点呢 。 。 。
----EOF----
最近在看Martin Fowler的《重构》一书,书中在讲临时变量的时候提到,编程的时候尽量不要去改变入参的值,因为这样的当时开发者来说是比较能理解的,但是对于后续维护者来说,这个就会比较头大。因为有时候我们根本就搞不明白为什么进入的时候是这样的,出来的为什么不是我要的值呢。因此, Martin Fowler建议如果要对入参做改变,可以定义一个返回值,然后把这个返回值重新复制给一个新的变量。
e.g.
public class Demo { static User user1 = new User("clu", 111); public static void main(String args[]){ User user2 = changeUser(user1); System.out.println(user2.getName()); } public static User changeUser(User user) { User userchanged = user; userchanged.setName("jack"); return userchanged; } }
output: jack
虽然,代码多了一些,但是对于后续的维护是有大大的好处的,毕竟程序是写给人看的。
为了不改变入参的值,Martin Fowler还建议要给形参加上final修饰符,这样这个变量就不会被强制复制了。
说到这里的时候,提到了java开发经常会出错的一个点就是:java 的值传递问题。
名词解释:
值传递:java程序中,调用者调用被调用者的时候,通常都是把调用者的参数的值“拷贝”一份来进行操作的。Java里面所有的传递都是按值传递
简单说就是上面的main方法在调用changeUser方法的时候,会进行user=user1的赋值操作。也就是说,实际上我们在changeUser里做的任何改动都是对拷贝出来的副本进行的改动,不会对原来的值user1造成任何影响。那么上面的代码为什么user的值被改变了呢? 我们这里的原因有两个,一个是我们在main方法里对user重新进行了赋值。 第二个我想说的是,这里即使没有赋值也就是代码变成这样:
public class Demo { static User user1 = new User("clu", 111); public static void main(String args[]){ // User user2 = changeUser(user1); System.out.println(user1.getName()); } public static User changeUser(User user) { User userchanged = user; userchanged.setName("jack"); return userchanged; } }
output也还是这样的
output: jack
下面就是解释一下原因:
例子1:基本数据类型:
//code from refactorying public static void main(String[] args){ int x = 5; triple(x); System.out.println("x after triple: " + x); } /** * @param x */ private static void triple(int x) { // TODO Auto-generated method stub x = x *3; System.out.println("x in triple : " + x); }
output: x in triple : 15 x after triple: 5
也就是说我们期望的想把5,放大3倍的效果并没有出来。为什么,因为在triple方法体内,我们是对另外一个副本进行了操作,但是在方法体外,x还是原来的那个值,除非,你对它重新赋值。
例如这样:
public static void main(String[] args){ int x = 5; x= triple(x); System.out.println("x after triple: " + x); } /** * @param x */ private static int triple(int x) { // TODO Auto-generated method stub x = x *3; System.out.println("x in triple : " + x); return x; }
例子2:引用类型(对象,数组):
对象:
public static void main(String[] args){ User user = new User("clu" , 123); rename(user); System.out.println(" user after User name: " + user.getName()); } /** * @param user */ private static void rename(final User user) { // TODO Auto-generated method stub if(user != null) { user.setName("xxxxxx"); } System.out.println(" user in User name: " + user.getName()); }
output: user in User name: xxxxxx user after User name: xxxxxx
你会发现username被修改了,你很知道,为什么,不是说只对副本进行操作么,这里的值为什么会被修改了呢。
那就让我来解释一下吧。 我们都知道对象是引用类型,那么引用类型,它的值是什么呢,它总有一个值吧,不然这个东西不可能凭空存在的啊。对的,引用对象它说存的值是一个对象实例在堆内存中的地址,也就是类似:0x0000F000的值,它也是一个值,只不过它指向了另一个内存区域。这就是为什么说java中只有按值传递的原因了。
我们这个例子中,我们调用rename方法,这时我们把user的引用地址copy了一份,但是,当我们执行.操作的时候,我们是去修改的这个引用所指向的真实内存空间里的值。 就好比我在淘宝上买了个电视,我把我家的地址copy一份给快递员,快递员拿到我家的地址之后,就往我家里送电视,等电视送到了,我家里就多了一个电视的。而不是别人的家里多了电视。
同样的,如果我改动代码如下,这里先把final去掉:
public static void main(String[] args){ User user = new User("clu" , 123); rename(user); System.out.println(" user after User name: " + user.getName()); } /** * @param user */ private static void rename( User user) { // TODO Auto-generated method stub // if(user != null) { // user.setName("xxxxxx"); // } user = new User("jack", 333); System.out.println(" user in User name: " + user.getName()); }
output就变成这样了。
output: user in User name: jack user after User name: clu
原因就是,user这个地址指向了其他人的地址,也就是我给了一个其他人的地址给快递员,东西并没有送到我家里来,那么我家里还是原来的样子,东西不会多也不会少。
数组也是一样的道理, 如下:
public static void main(String[] args) { int[] count = { 1, 2, 3, 4, 5 }; change(count); System.out.println(" user after User name: " + count[0]); } /** * @param count */ private static void change(int[] count) { // TODO Auto-generated method stub count[0] = 6; }
output:
user after User name: 6
这个值会被改变就等同于,user的真实内存内容被修改了。
例子3:final修饰
public static void main(String[] args) { User user = new User("clu", 1111); rename(user); } /** * @param user */ private static void rename(final User user) { if(user != null) { user.setName("xxxxxx"); } // user = new User("jack", 333); System.out.println(" user in User name: " + user.getName()); }
当用final修饰时,这时的形参是不能被改变的,也就是这里那个注释掉的代码是不能被执行的,原因就是地址是不能再被重新赋值的,而setter能执行就说明,当我们执行setter的时候,并没有对这个地址值本身进行操作,而是对这个地址所指向的堆内存进行了操作。
不知道各位有没有跟理解一点呢 。 。 。
----EOF----