`
xfxlch
  • 浏览: 165953 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

Java的按值传递

    博客分类:
  • Java
阅读更多
问题:
最近在看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:按值传递还是按引用传递详细解说

    ### Java中的按值传递与按引用传递详解 #### 一、引言 在Java编程语言中,关于参数传递的方式一直存在两种观点:一种认为Java仅支持按值传递,另一种则指出Java同时支持按值传递和按引用传递。实际上,这两种观点...

    java按值传递还是按引用传递详细解说[收集].pdf

    在Java中,所有的参数传递都是按值传递,但这并不意味着对象变量也是如此。这是因为Java中对象的引用是按值传递的,而对象本身则是按引用访问的。以下是对这个概念的详细解释。 1. **按值传递**: 在Java中,当...

    解析Java按值传递还是按引用传递

    "Java按值传递还是按引用传递" Java是一种面向对象的编程语言,在Java中,对于方法的参数传递有两种方式:按值传递和按引用传递。这两种方式都有其特点和应用场景,本文将对这两种方式进行详细的介绍和分析。 一、...

    java中只有值传递

    Java中传递对象时传递的并不是对象中的内容, 而是对象的地址。

    java题的小总结按值传递还是地址传递

    本文将详细解释Java中按值传递和按引用传递的概念,并通过示例来帮助理解这两种方式的区别。 1. 按值传递(Pass by Value) 在Java中,基本数据类型(如int、float、char等)的参数传递是按值传递。这意味着当一个...

    Java按值传递和按址传递(面试常见)

    Java编程语言中的参数传递主要有两种方式:按值传递(pass by value)和按引用传递(pass by reference),尽管Java官方并不支持真正的按引用传递,但其行为类似于按引用传递。这两种传递方式在面试和笔试中经常出现...

    java值传递与引用传递

    在Java中,基本数据类型(如int、char、float等)就是按值传递的。例如,如果你有一个整型变量`int a = 5;`,并将其作为参数传递给一个函数,函数内部对这个参数的任何修改都不会影响到原始变量`a`的值,因为传递的...

    java 值传递和引用传递的比较

    在Java编程语言中,了解值传递和引用传递的概念至关重要,因为它们直接影响到函数参数的处理方式。下面将详细探讨这两个概念及其区别。 首先,我们来理解什么是值传递。在Java中,基本数据类型(如int、double、...

    Java是值传递,传对象引用也是通过值

    标题“Java是值传递,传对象引用也是通过值”揭示了Java中参数传递的核心概念。Java总是以值传递方式进行,这意味着当你将一个变量作为参数传递给方法时,传递的是该变量所存储值的一个副本。然而,对于对象类型的...

    Java的按值传递和按引用传递分析.rar

    Java编程语言中有两种参数传递方式:按值传递和按引用传递。理解这两种方式对于编写高效、无误的代码至关重要。 1. **按值传递(Pass by Value)** - Java中的基本类型(如int, double, char等)是按值传递的。这...

    JAVA参数传递方式实例浅析【按值传递与引用传递区别】

    JAVA 参数传递方式实例浅析【按值传递与引用传递区别】 JAVA 参数传递方式实例浅析【按值传递与引用传递区别】是 JAVA 编程语言中的一种基本概念,对于 JAVA 开发者来说是非常重要的。本文将通过实例形式分析 JAVA ...

    java html 值传递

    在本教程中,我们将探讨如何在Java和HTML的结合中实现值传递,以便在网页上绘制圆圈,这对于初学者来说是一个很好的实践项目。 首先,我们要理解Java和HTML之间的交互。通常,这种交互是通过Servlet或JSP(Java...

    Java面向对象值传递和引用传递

    Java 面向对象值传递和引用传递 Java 面向对象编程中,参数传递是非常重要的一个概念。参数传递有两种方式:值传递和引用传递。了解这两种方式的区别是非常重要的,因为它们对程序的执行结果产生了很大的影响。 值...

    为什么Java只有值传递

    我们先看一下值传递和引用传递的概念和区别 值传递:是指在调用函数时将实际参数复制一份传递到函数中,...我们通过例子理解一下Java的值传递: public static void main(String[] args) { int a = 10; int b = 20;

    Java中的按值传递和按引用传递的代码详解

    Java中的按值传递和按引用传递的代码详解 本文通过实例代码详细解释了Java中的按值传递和按引用传递的相关知识。通过实验,我们可以了解Java中基本类型和引用类型的传递机制。 按值传递 在Java中,基本类型的变量...

    详解java的值传递、地址传递、引用传递

    详解java的值传递、地址传递、引用传递 java是一种面向对象的编程语言,它的参数传递机制是值传递的,而不是地址传递或引用传递。很多开发者对java的值传递和地址传递存在误解,认为java中的基本数据类型是值传递,...

    Java 值传递Visio资源

    在Java中,参数传递有两种方式:值传递和引用传递。这个“Java 值传递Visio资源”包含了几个Visio图形文件,帮助我们直观地理解这两种传递方式。 1. **值传递**: 当方法调用时,对于基本类型(如int, double, char...

    java学习java语言的值传递和引用传递

    java学习java语言的值传递和引用传递

Global site tag (gtag.js) - Google Analytics