Java:理解Java中函数参数传递的【按值传递】和【按引用传递】

开篇:

Java的函数参数传递实际上不存在所谓的按引用传递和按值传递,说白了都是按值传递。

 

Java基本类型可以分为三类:

字符类型char,布尔类型boolean以及数值类型byte、short、int、long、float、double。数值类型又可以分为整数类型byte、short、int、long和浮点数类型float、double。

 

理论解释:

 

Java中函数参数传递时,作为基本类型,是直接产生一个拷贝值作为内部参数使用的。这个拷贝的值,只有在值的层面上与原来的输入参数有相同,在内存地址上已经不再指向原来的参数地址了,而是和拷贝出来的值相关联了,这点可以从普通变量的定义来理解。因此在函数内部修改这个拷贝的值,并不会影响原来输入参数的值,即不会动到那块内存。

作为对象引用类型,也是产生一个拷贝值,但是这个拷贝值是输入参数引用的拷贝值,是一个内存地址,这个拷贝值与输入参数的引用指向的内存地址是一个数值,也就是同一块内存地址。既然是同一块内存地址,名义上是产生了一个引用的拷贝值,事实上指向的是同一块内存地址,那么修改这个拷贝的引用指向的内存地址的数据,当然也就是直接修改了原来的引用指向的内存的数据,从而原来的数据也被修改,但是其引用本身没有被修改,所以一般如果不想改变输入参数用final修饰保证编译器能检查出意外修改输入参数的问题。

结果是引用没有被修改,但是引用的数据被修改。

 

根据编译器的优化原理,一般情况下,对于输入参数,编译器只需要一个整数就可以满足传递参数的目的,这个整数是一个内存地址。当是基本类型时,由于代价很小,就是直接复制一份参数作为内部参数使用。而对于对象引用,则只需要复制一份对象引用,既然是复制了对象引用,那么此时引用指向的内存地址(对象)是一样的,存储这个引用本身的内存地址则是不同的(变量自身需要内存分配才能产生变量,以定义该变量)。

 

因此,java中参数传递都是按所谓的“按值”传递,只不过,一个是基本类型的值(拷贝),一个是引用的值(拷贝),而非引用的对象。

public class HelloWorld
{
	String str = new String("good");//创建一个 内容为 "good"的字符串对象,str 引用指向该对象 ,运行期产生"good"字符串对象。
	String str2 = "good"; //str2指向常量池中"good"的字符串对象。编译时产生"good"字符串
	//此时str/str2分别是两个不同的引用,(str==str2)为false
	//碰巧指向内容相同的字符串。
	StringBuffer str3 = new StringBuffer("good");//创建"good"的StringBuffer对象,str3为指向该字符串的引用。
	char[] ch = { 'a', 'b', 'c' };//初始化char数组,ch为指向该数组的引用
	Integer i = 2;//初始化数值为2的Integer对象,i为指向该对象的引用

	public void change(Integer i)
	{
		//i为传入参数的一个引用的拷贝,也就是此时i变量的指向的内存地址和传入参数的指向的内存地址是相同的,但是保存这个临时参数的内存是另外一个地方,与原始参数不是同一个内存(因为拷贝了,肯定是另外一个内存了)
		//此时也指向了原来的内存地址
		i = 10;//试图改变原始i指向的对象的值,这里是将i重新引用到一个值为10的Integer对象
		//函数结束时,i指向的对象(内存)被销毁,i自身也被销毁。
		//原始的i并未被改变
	}

	public void change(String str, char ch[])
	{		
		//str,ch传入时,均会自动产生一个对应引用的拷贝,下面的操作是针对这个拷贝的
		//在未做任何修改前,都是指向了原始引用参数指向的对象
		str = "I'm changed";//试图改变原始引用指向的对象内容,但是这里实际上是将该引用重新指向了一个新的String对象
		str = str + "changed?";//由于String是immutable类,【+】 操作不会修改str的拷贝之前指向的对象的内容,这里是
		//重新产生了一个新的String对象,然后将str的拷贝指向它。
		ch[0] = 'g';//改变原始引用的指向的对象内容,由于ch[0]为基本类型,即是将该ch[0]指向的内存地址的值修改。
		//函数退出时,临时拷贝将被回收,临时创建的对象由于不再使用也被回收。
		//但是ch[0]已经改变了原始对象所在的内存的值。
	}

	public void change(StringBuffer str, char ch[])
	{
		//str,ch传入时,均会自动产生一个对应引用的拷贝,下面的操作是针对这个拷贝的
		//在未做任何修改前,都是指向了原始引用参数指向的对象
		str.append("changed");//由于StringBuffer.append操作会修改自身的内容(mutable)
		//也即会修改str指向的对象的内容,因此会直接修改原始引用参数对应的对象的内容
		ch[0] = 'g';//改变原始引用的指向的对象内容,由于ch[0]为基本类型,即是将该ch[0]指向的内存地址的值修改。
		//函数退出时,临时拷贝将被回收,临时创建的对象由于不再使用也被回收。
		//但是ch[0]已经改变了原始对象所在的内存的值。
	}

	public static void main(String[] args)
	{
		// TODO Auto-generated method stub
		System.out.println("===================华丽的分割线========================");
		String s1 = "Hello";
		String s2 = s1;
		s1.replace('e', 'a');
		System.out.println(s1);
		System.out.println(s2);

		HelloWorld h = new HelloWorld();
		h.change(h.str, h.ch);
		System.out.println("===================华丽的分割线========================");
		System.out.println("1:" + h.str + " and " + String.valueOf(h.ch));
		h.change(h.str2, h.ch);
		System.out.println("===================华丽的分割线========================");
		System.out.println("2:" +h.str2 + " and " + String.valueOf(h.ch));
		System.out.println("===================华丽的分割线========================");
		h.change(h.str3, h.ch);
		System.out.println("3:" +h.str3 + " and " + String.valueOf(h.ch));
		System.out.println("===================华丽的分割线========================");
		h.change(h.i);
		System.out.println("4:" + h.i);
	}

}
//结果
===================华丽的分割线========================
Hello
Hello
===================华丽的分割线========================
1:good and gbc
===================华丽的分割线========================
2:good and gbc
===================华丽的分割线========================
3:goodchanged and gbc
===================华丽的分割线========================
4:2


注意上面示例代码中,作为Integer/String等不可变类和作为StringBuffer的可变类的结果不同。


因此,对象的引用作为参数传入以后会不会修改原始的对象内容,完全取决于该对象的类的实现,实现为immutable的非可变类,比如Integer,String等封装了基本类型的类,是不会修改原始对象的内容的(经常是新产生一个对象返回给调用者);而作为可变类的StringBuffer,由于相应的操作实现是会修改到引用对象的内容,从而会修改到原始对象的内容(因为引用的拷贝和引用都指向了该对象)。

 

总而言之,

Java的函数参数传递实际上不存在所谓的按引用传递和按值传递,说白了都是按值传递,只不过这个值所代表的意义要搞清楚。与C++/C里面的原理大致相同,因为编译器原理也基本相同嘛。

结合对象和引用管理的内存概念和编译器原理来理解,比较好理解这里的各种容易混淆的地方。


 参考:

http://www.yoda.arachsys.com/java/passing.html


你可能感兴趣的:(Java:理解Java中函数参数传递的【按值传递】和【按引用传递】)