前些天在看论坛上有人在为Java的传参方式而讨论。那么Java中的参数到底是按引用传递还是按值传递?

我的解释是按值传递的。在Java里参数是按值来传递的,对于基本的数据类型很容易理解就不解释了。比较难理解的可能是Java传递的是对象的引用,但这些引用是按值传递。

这里涉及一点JVM的知识了。Java定义对象的目的就是使用对象。一般我们是通过栈(虚拟机栈)上的reference类型的数据来操作具体的存在与堆上的对象。那么访问对象的方式也是取决于JVM虚拟机的。HotSpot虚拟机使用的是直接地址的方式来访问对象。(另一种方式是使用句柄的方式)

java 如何传参一个未知类型的对象 java传参用map还是对象_内存地址

如上图所示:Java堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而reference中存储的直接就是对象的地址。以上是通过JVM的底层构建方式来解释对象的引用是按值传递的。



接下来通过代码的方式来解释说明,查看源代码打印帮助


public static void main( String[] args ){
    Dog aDog = new Dog("Max");
    foo(aDog);
    if( aDog.getName().equals("Max") ){ //true
        System.out.println( "Java passes by value." );
    }else if( aDog.getName().equals("Fifi") ){
        System.out.println( "Java passes by reference." );
    }
}
public static void foo(Dog d) {
    d.getName().equals("Max"); // true
    d = new Dog("Fifi");
    d.getName().equals("Fifi"); // true
}




在这个例子里面,执行完foo()方法之后,在main方法里再调用aDog.getName()方法依然会返回”Max”,

在main方法中的aDog并没有因为foo()的执行而被重写,这说明了参数是按值来进行传递的。如果是按照引用来传递的话在执行完foo()方法之后aDog.getName()将会返回”Fifi”。


就像这样:



Dog aDog = new Dog("Max");
foo(aDog);
aDog.getName().equals("Fifi"); // true
 
public void foo(Dog d) {
    d.getName().equals("Max"); // true
    d.setName("Fifi");
}



在Java的规范里说明了在Java中一切参数都是按值传递的,根本就没有引用传递这一说。


理解这个概念的关键是要明白


Dog myDog;


这里声明的并不是一个Dog对象,而是一个指向Dog对象的指针(引用)。


这是什么意思呢,就是当你执行


Dog myDog = new Dog("Rover");//myDog实际上就是Rover对象的地址值


foo(myDog);


本质上是你把创建好的Dog对象的地址值传递给foo方法。(我说的‘本质上’其实是因为Java中的指针并不是直接的地址,不过可以简单的理解成这样)。


假设Dog对象在内存中的地址是42。那我们就是把42这个值传递给了foo方法。


如果foo方法的定义如下:


public void foo(Dog someDog) {


    someDog.setName("Max");     // AAA


    someDog = new Dog("Fifi");  // BBB


    someDog.setName("Rowlf");   // CCC


}




让我们来看看执行的时候会发生些什么。


1. someDog的值设置为42。


2. 在AAA行


      a.someDog指向一个内存地址为42的Dog对象。


      b.把Dog(内存地址为42)对象的name属性改为Max。


3. 在BBB行


      a.一个新的Dog对象被创建,我们假设它的内存地址是74。


      b.把这个74的内存地址值赋给someDog。


4. 在CCC行


      a.someDog指向一个内存地址为74的Dog对象。


      b.把Dog(内存地址为74)对象的name属性改为Rowlf。


5. 方法执行完毕。


现在让我们来想想在这个方法外面发生了什么:


myDog改变了吗?这个问题的关键在于:


      要明确myDog是一个指针,而不是一个实际的Dog对象。所以答案是它没有改变,myDog的值还是42;它指向的还是最开始的那个Dog对象(虽然在foo方法中的AAA行把它指向对象的name属性改成了Max,但是它指向的还是那个最初的Dog对象)。


这验证了改变所指对象的属性,但没有改变其指向。


Java的运行机制跟C很像。你可以给一个指针赋值,然后把这个指针传递给一个方法,之后在这个方法中你可以改变这个指针指向对象的数据,但是你不能改变这个指针的指向。




在C++,Ada,Pascal以及其他支持引用传递的语言中你可以直接改变传递的参数。如果Java是引用传递的话,那么在执行上面定义的foo方法的BBB行的时候someDog的指向就会被改变。可以把引用参数当成被传递参数的别名,当这个别名被赋值的时候就相当于被传递的参数被赋值。