欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java的按值传递 博客分类: Java java按值传递 

程序员文章站 2024-03-13 14:16:39
...
问题:
最近在看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----

相关标签: java 按值传递