今天,我在一本面试书上看到了关于java的一个参数传递的问题:写道html
java中对象做为参数传递给一个方法,究竟是值传递,仍是引用传递?java
我毫无疑问的回答:“引用传递!”,而且还以为本身对java的这一特性非常熟悉!程序员
结果发现,我错了!面试
答案是:编程
值传递!Java中只有按值传递,没有按引用传递!数组
回家后我就火烧眉毛地查询了这个问题,以为本身对java这么基础的问题都搞错实在太丢人!编程语言
综合网上的描述,我大概了解了是怎么回事,如今整理以下,若有不对之处望大神提出!函数
先来看一个做为程序员都熟悉的值传递的例子:spa
... ... //定义了一个改变参数值的函数 public static void changeValue(int x) { x = x *2; } ... ... //调用该函数 int num = 5; System.out.println(num); changeValue(num); System.out.println(num); ... ...
答案显而易见,调用函数changeValue()先后num的值都没有改变。指针
由此作一个引子,我用图表描绘一个值传递的过程:
num做为参数传递给changeValue()方法时,是将内存空间中num所指向的那个存储单元中存放的值,即"5",传送给了changeValue()方法中的x变量,而这个x变量也在内存空间中分配了一个存储单元,这个时候,就把num的值5传送给了这个存储单元中。此后,在changeValue()方法中对x的一切操做都是针对x所指向的这个存储单元,与num所指向的那个存储单元没有关系了!
天然,在函数调用以后,num所指向的存储单元的值仍是没有发生变化,这就是所谓的“值传递”!值传递的精髓是:传递的是存储单元中的内容,而非地址或者引用!
接下来,就来看java中的对象参数是怎么传递的:
一样,先给出一段代码:
... ... class person { public static String name = "Jack"; ... ... } ... ... //定义一个改变对象属性的方法 public static void changeName(Person p) { p.name = "Rose"; //若是这么写:会发现打印出来的person.name是没有发生变化的 //p = new Person();p.name = "Rose";//这么写会致使引用的副本指向新的对象,已经和原先指向的对象没一点关系 } ... ... public static void main(String[] args) { //定义一个Person对象,person是这个对象的引用 Person person = new Person(); //先显示这个对象的name属性 System.out.println(person.name); //调用changeName(Person p)方法 changeName(person); //再显示这个对象的name属性,看是否发生了变化 System.out.println(person.name); }
答案应该你们都心知肚明:
第一次显示:“Jack”
第二次显示:“Rose”
方法用了一个对象参数,该对象内部的内容就能够改变,我以前一直认为应该是该对象复制了一个引用副本给调用函数的参数,使得该方法能够对这个对象进行操做,实际上是错了!
http://www.cnblogs.com/clara/archive/2011/09/17/2179493.html 写道
Java 编程语言只有值传递参数。当一个对象实例做为一个参数被传递到方法中时,参数的值就是该对象的引用一个副本。指向同一个对象,对象的内容能够在被调用的方法中改变,但对象的引用(不是引用的副本)是永远不会改变的。
为何这里是“值传递”,而不是“引用传递”?
我仍是用图表描绘比较能解释清楚:
主函数中new 了一个对象Person,实际分配了两个对象:新建立的Person类的实体对象,和指向该对象的引用变量person。
【注意:在java中,新建立的实体对象在堆内存中开辟空间,而引用变量在栈内存中开辟空间】
正如如上图所示,左侧是堆空间,用来分配内存给新建立的实体对象,红色框是新建的Person类的实体对象,000012是该实体对象的起始地址;而右侧是栈空间,用来给引用变量和一些临时变量分配内存,新实体对象的引用person就在其中,能够看到它的存储单元的内容是000012,记录的正是新建Person类实体对象的起始地址,也就是说它指向该实体对象。
这时候,好戏上台了:
调用了changeName()方法,person做为对象参数传入该方法,可是你们特别注意,它传入的是什么!!!person引用变量将本身的存储单元的内容传给了changeName()方法的p变量!也就是将实体对象的地址传给了p变量,今后,在changeName()方法中对p的一切操做都是针对p所指向的这个存储单元,与person引用变量所指向的那个存储单元再没有关系了!
回顾一下上面的一个值传递的例子,值传递,就是将存储单元中的内容传给调用函数中的那个参数,这里是否是殊途同归,是所谓“值传递”,而非“引用传递”!!!
那为何对象内部可以发生变化呢?
那是由于:p所指向的那个存储单元中的内容是实体对象的地址,使得p也指向了该实体对象,因此才能改变对象内部的属性!
这也是咱们大多数人会误觉得是“引用传递”的终极缘由!!!
下面再举个例子:
public class Example { String str = new String("good"); char[] ch = { 'a', 'b', 'c' }; public static void main(String args[]) { Example ex = new Example(); ex.change(ex.str, ex.ch); System.out.print(ex.str + " and "); System.out.print(ex.ch); } public void change(String str, char ch[]) { str = "test ok"; ch[0] = 'g'; } }
疑问:这段代码为何 str没被改变,可是ch[0]却改变了?
解释:
(1)首先, String 和 char数组 都是引用类型,不是基本类型。
(2)传进去的引用类型的参数(例如str),传进去的时候至关于新建了一个变量var,已经不是原来的变量了,可是他们指向的数据区域都同样(数据地址相同),因此若是你改变了str指向的数据区域,那也只是改变var的数据地址,没有改变str的数据地址,str仍是指向原来的数据区域 。而 str = "test ok"; 这一句,就是改变了内部str指向的数据区域(改变数据地址),它再也不指向"good"对象,而是指向一个新对象"test ok",至关于str = new String("test ok");,这只在函数内部有效,外部的str的数据地址没有变,仍是指向原来"good"对象;而ch[0] = 'g';这一句,意思把内部ch指向的数据区域(也就是实际存放数组内容的地方)里面的第一个字符改为g,仍是在原来指向的数据区域上操做,并无改变内部ch的数据地址,因此这个修改也会反映到外部的ch。
(3)能够试一下在函数里面的 ch[0] = 'g'; 前面加多一句 ch = {'a','b','c'}; ,这时候就改变内部ch的数据地址了,它的内容虽然仍是abc,可是已经指向一个新的数据区域。你在外部再打印ch就会发现内容没有改变。
解释下:java内存的堆内存和栈内存
Java把内存分红两种,一种叫作栈内存,一种叫作堆内存
在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配。当在一段代码块中定义一个变量时,java就在栈中为这个变量分配内存空间,当超过变量的做用域后,java会自动释放掉为该变量分配的内存空间,该内存空间能够马上被另做他用。
堆内存用于存放由new建立的对象和数组。在堆中分配的内存,由java虚拟机自动垃圾回收器来管理。在堆中产生了一个数组或者对象后,还能够在栈中定义一个特殊的变量,这个变量的取值等于数组或者对象在堆内存中的首地址,在栈中的这个特殊的变量就变成了数组或者对象的引用变量,之后就能够在程序中使用栈内存中的引用变量来访问堆中的数组或者对象,引用变量至关于为数组或者对象起的一个别名,或者代号。
引用变量是普通变量,定义时在栈中分配内存,引用变量在程序运行到做用域外释放。而数组&对象自己在堆中分配,即便程序运行到使用new产生数组和对象的语句所在地代码块以外,数组和对象自己占用的堆内存也不会被释放,数组和对象在没有引用变量指向它的时候,才变成垃圾,不能再被使用,可是仍然占着内存,在随后的一个不肯定的时间被垃圾回收器释放掉。这个也是java比较占内存的主要缘由,实际上,栈中的变量指向堆内存中的变量,这就是 Java 中的指针!