Java方法参数的传递机制,值传递?引用传递?

时间:2022-08-04 21:26:00

在调用某个方法时你是不是经常有如下2个疑惑,
1、明明传进去的参数在方法里做了值的改变,但方法调用完后传进去的参数在后面的代码中值却没有发生变化,疑惑中。(期望变化却没有发生变化)
2、一个对象在作为参数被方法调用之后,突然发现在后面的代码中值发生了变化,疑惑中。(不期望变化却发生了变化)

为了解决这些疑惑,请执行下面的代码,注意println代码部分,看看结果是不是你期望的。

例1:
public class ParamTransfer {  
public void chgValue(int param){
param = param + 1;
System.out.println(param);
}


public static void main(String[] args) {
int value = 1;
ParamTransfer pt = new ParamTransfer();
pt.chgValue(value);
System.out.println(value);
}

}
例2:
public class ParamTransfer{
public void chgValue(StringBuffer param) {
param = param.append("World!");
System.out.println(param);
}


public static void main(String[] args) {
ParamTransfer pt = new ParamTransfer();
StringBuffer value = new StringBuffer("Hello ");
pt.chgValue(value);
System.out.println(value);
}
}
例3:
public class ParamTransfer{
public void chgValue(StringBuffer param) {
StringBuffer sb = new StringBuffer("Hi ");
param = sb;
param.append("World!");
System.out.println(param);
}


public static void main(String[] args) {
ParamTransfer pt = new ParamTransfer();
StringBuffer value = new StringBuffer("Hello ");
pt.chgValue(value);
System.out.println(value);
}
}

如果这三个例子的结果完全在你意料之中,那么恭喜你可以不用再看我后面的废话了。如果不是请继续。。。
第一个和第二个例子大家也许觉得并不意外,第三个例子可能会有某些人迷惑了。。。为什么呢?
回头看看这篇文章的题目,值传递?引用传递?其实,我这么起名字只是为了吸引你的眼球,在我看来,根本不存在什么值还是引用的传递,而是类型传递,即基本数据类型(不能new只能直接赋常量值或同类型变量赋值)和对象类型(也就是可以new的类)的传递。
下面,我们不妨把上面的代码改造一下,把调用方法的步骤直接变成main中的代码,还原代码的真面目,发现没有,其实类型传递就是变量赋值!
public class ParamTransfer {
public static void main(String[] args) {
int value = 1;
int param = value;
param = param + 1;
System.out.println(param);
System.out.println(value);
}
}


public class ParamTransfer {
public static void main(String[] args) {
StringBuffer value = new StringBuffer("Hello ");
StringBuffer param = value;
param = param.append("World!");
System.out.println(param);
System.out.println(value);
}
}


public class ParamTransfer {
public static void main(String[] args) {
StringBuffer value = new StringBuffer("Hello ");
StringBuffer param = value;
StringBuffer sb = new StringBuffer("Hi ");
param = sb;
param = param.append("World!");
System.out.println(param);
System.out.println(value);
}
}


对于基本数据类型来说,因为不存在new的问题,值的改变就是内存地址指向的改变,值相同,内存地址就相同,值不同,内存地址就不同。这就是为什么基本数据类型只要值相同(不管是怎么赋的值)那么==判断就为真的原因(反之即为假)
而对于对象类型来说(String类为特例,后有详述),值的改变有两种,第一是内存中的值发生了改变,例2就是这种。第二是指向了新的内存地址,例3就是这种。
但不论是那种值的改变,对象变量的赋值不会改变原有变量即主值变量的引用地址!这也是为什么例1和例3传递变量的值不随参数变量变化的原因。而例2为何变化,很好理解,因为传递变量和参数变量引用的是同一地址,而值的改变是上面说的第一种。


但是,有一个特例,那就是String这个类对象,我们例2改造下。
public class ParamTransfer {
public void chgValue(String param) {
param = param.concat("World!");
System.out.println(param);
}


public static void main(String[] args) {
ParamTransfer pt = new ParamTransfer();
String value = new String("Hello ");
pt.chgValue(value);
System.out.println(value);
}
}


运行一下,是不是有了新的疑惑?明明是对象类型传递,又是第一种值改变,为何传递参数的值没有跟着改变?这就是前面说的的String类的特例,这个牵扯到java编译器对字符串常量优化的问题,也就是经典的
String str1 = "abc";  和 String str2 = new String("abc");有何不同的问题。但这已不是本文讨论的重点,有兴趣的朋友可以参考如下博文:
http://blog.sina.com.cn/s/blog_798b04f90100ta67.html


最后,如果你对上面的论述还是不太理解,那就只好扒一扒java内存的概念了,由于这也不是本文的重点,我就转载一段文字吧。
------------------------------以下内容为转载,由于出处不详,无法注明原创,请谅解-------------------------------
1.java程序运行时有6中地方存储数据,分别是:寄存器、栈、堆、静态存储、常量存储、非RAM(随机存储器),主要是堆与栈的存储。 
2.堆与栈是java用来在RAM中存储数据的地方,java自动管理堆和栈,程序员不能直接设置堆和栈。 
3.栈的优势是:存取速度比堆要快,仅次于直接位于cpu中的寄存器;栈数据可以共享。   但缺点是:存在栈中数据大小与生命周期必须是确定的,缺乏灵活性。 
4.堆的优势在于可以动态分配内存大小,生存期也不必事先告诉编译器,java的垃圾收集器会自动收走这些不再使用的数据,缺点是由于要在运行时动态分配内存,存取速度较慢。 5.基本数据类型的存储,java的基本数据类型共有8种:int,short,byte,long,float,double,boolean,(基本数据类型中并没有String的基本类型)。这种类型如int=3的形式来定义,称为自动变量。自动变量存在的是字面值,即不是类的实例,也不是类的引用。a 是一个指向int类型的引用,指向3这个字面值。这些字面值的数据由于大小可知,生存期可知(这些字面值固定定义在某个程序块里面,程序块退了,字段值就消失了),处于追求速度的原因就存在栈中。 
6.另外栈有一个很重要的特殊性,就是存在栈中的数据可以共享。如 需要定义int a = 3; int b =3;这两个自动变量。编译器先处理int  a=3;首先在栈中创建一个变量为a的引用,然后查找栈有没有字面值为3的引用,没有找到,就开辟一个存放3这个字面值的地址,然后将a指向3的地址。接着处理int b=3;在创建完b这个引用变量后,由于在栈中已经有了3这个字面值,即将b直接指向3的地址。这样,就出现了a和b同事指向3的情况。 定义完a与b后,在令a=4,那么b不会等于4,而是等于3,。在编译器内部,遇到时,它就会中新搜索栈中是否有4这个字面值,如果没有,重新开辟地址存放4的值。如果已经有就直接将a指向这个地址,因此a的值改变不会影响b的值。  
7.对象的内存模型.创建一个对象包括对象的声明和实例化两步:声明对象的引用和对象的实例化。声明一个对象引用时,将在栈内存为对象的引用变量分配空间;对象实例化是,在堆内存中为类成员变量分配内存,并将其初始化为各数据类型默认值,接着进行显示初始化,最后调用构造方法为成员变量赋值,返回堆内存中对象的引用(相当于首地址)给应用变量,通过引用变量来引用堆内存中的对象。 
8.包装类数据的存储:基本数据类型的定义都是直接在栈中,如果是包装类型来创建对象,就和普通对象一样。 
9.string数据类型是一种特殊数据类型,既可以用基本数据类型格式来创建,也可以用普通基本类型来创建。