Java中参数传递时值传递的机制分析

参数传递是什么?
     在C的函数或是JAVA的方法中,向一个函数或方法内部传递一个参数,比如:
 
void fun( int num ){
    num+=2 ;
}
 
int a = 3 ;
fun( a ) ;
 
     这个a就被作为参数传入函数fun()中,作为a,然后返回或者不返回值
 
     回到最初,函数的作用是复用,那么我们希望这个参数传递是什么样的呢?就是假如我们去掉函数的外衣,就让函数变成代码放到之前是函数的地方,那么很自然这里最后b的值会被改变,这可以说是最朴实的参数传递了,自然的样子。
 
     但是人们又发明了另一种方式
     其实我认为这是人们重新定义函数,把函数只作为一段代码的复用上升到一个工具,函数不是作为调用函数代码的一部分,而是把函数作为一个工具,一个函数只能以返回值的方式影响调用代码(这里不考虑在函数体内能跟调用代码中除了参数以外的代码沟通,因为java中泛滥了,其实java中能取到的值也是类中的而不是调用代码的)的,这样更加安全。
     这种方式采用的方法是复制一个新值放到函数中去运算,就像:在调用时初始化的局部变量。我们知道局部变量的生存空间只有函数内,这样函数内部的操作就不会影响到外面。
     这种方式被称为值传递。 那它是怎么实现的呢?看下面。
 
上面说了复制一个新值,其实这个东西很有门道。
     先从硬件说起。
     所有的代码在跑的时候,里边数据都是放在内存中的,包括文件啊什么的都需要从硬盘啊SD卡中取出来,放到内存中,也就是说你所有的值啊,对象啊,都在内存中。
     而内存中放这些东西的地方分为两块:堆、栈
     很俗套的情节:其中栈的位置小,速度快,堆的位置大,速度慢
     于是很自然会有一套放东西的规划,小的数据,用的多的数据,放栈中,大的数据,用得少的数据,放堆中
 
     具体在JAVA中是这样的:
  • JAVA中所有的数据类型有9种,8种基本类型和1种对象类型,对象类型又分为系统自建和用户自建
  • int、long、double、float、byte、boolean、char 这七种基本类型的数据是直接放在栈中
  • string基本类型和所有的对象类型的数据(这里的数据指的是实例化的对象,空的类不是数据)放在堆中,在栈中存放在堆中的指针
 
     所以如果要说复制一个新值,对于只存在栈中和存放在在栈和堆中的数据来说,情况是不一样的。
     说复制之前,要说一下创建,创建一个值的时候发生了什么?同样是分两种情况:
  1. 创建一个存放在栈中的数据
    1. 比如:int a = 3 ; 这一句其实是两步:int a ; a = 3 ;
    2. 第一步 int a 是在栈中找了并且开辟了一个放int类型的存储空间,然后把这个存储空间跟变量名a绑定起来了(我自定义绑定的机制发生在命名空间中);
    3. 第二步是把3这个值存入a绑定的存储空间中。
  2. 创建一个 指针存放在栈中,内容存放在堆中 的数据
    1. 比如:Apple myapple = new Apple() ; 这一句也是两步:Apple myapple ; myapple = new RedApple() ;
    2. 第一步是在栈中找了并且开辟了一个存储空间,因为声明不是基本类型,所以它开辟了一个指针类型的存储空间,然后把这个存储空间跟对象名myapple绑定起来了;
    3. 第二步是(这里特意用了多态的属性)先在内存的堆中开辟一个能够存放RedApple对象的空间(这个空间没有绑定命名空间),然后经过类型检查是否合法之后,把这个空间的地址存放在跟myapple绑定的指针空间中
     数据在内存中的状态就像这样:
     
 
     好,回到开始,那么复制一个新值到函数中运算的复制的真实情况是什么样子?
     假如是这两个函数:
 
           
 
     传参的过程其实是一个赋值的过程:
  • fun(a); 这句执行的时候,传参其实是执行了这么两句:int num ; num = a ;
    • 在栈中开辟了一个int类型int大小空间,并且绑定num,然后把a绑定的栈空间中的数据复制一份放到num绑定的栈空间中
  • fun(myapple)这句执行的时候也是一样:Apple apple ; apple = myapple ;
    • 在栈中开辟了一个Apple类型指针大小空间,并且绑定apple,然后把myapple绑定的栈空间的数据(是地址啊)复制衣服放到apple绑定的栈空间中
现在数据在内存中的状态就像这样:
 
这种在传参时,把a绑定的栈空间中的数据复制一份放到num绑定的栈空间中 的行为就叫做值传递(同样发生在=运算符赋值运算符的时候)。
 
不过你要注意,你也能很清晰看到,所谓值传递之后,在函数中对变量进行操作会不会影响到调用函数代码中的变量,是不一定的,需要看传的是值还是地址,也就看真实数据是放在哪里,真实数据放在堆中传递的是栈中的地址时,就能被改变。
 
不过其实如果传入的时地址,能不能改变也要看是什么操作。
以上面的apple.dosomesth() 为例:
  • 假如这个方法能对apple的某些值进行操作,那么调用函数的代码中的myapple的内容也会受到改变
  • 但如果是赋值,也就是JAVA中的赋值。那么,它进行的还是值传递,受改变的是在局部变量中new出新对象的栈空间的地址,可能换成了新的地址,但是原来那个地址链接的堆空间的数据不受影响,也就是调用函数的代码中的myapple的内容完全不受影响

 

“在Java里面参数传递都是按值传递”这句话的意思是:按值传递是传递的值的拷贝,按引用传递其实传递的是引用的地址值,所以统称按值传递。
值传递的精髓是:传递的是存储单元中的内容,而非地址或者引用!
 
容易误解JAVA中这种传递了地址的情况是引用传递,其实不是的,不能看结果,而应该看过程的性质。JAVA中能传递地址,但是是通过值传递的方式!
而如果是引用传递,其实传递的应该是另一个地址

 

posted @ 2015-10-02 02:07  赛艇队长  阅读(468)  评论(0编辑  收藏  举报