Java值传递与引用传递

来源:互联网 发布:淘宝网柒牌男装 编辑:程序博客网 时间:2024/04/29 10:57

Jvm中堆与栈存储区别

 

Heap(堆)

Stack(栈)

JVM中的功能

内存数据区

内存指令区

存储数据

对象实例

基本数据类型,指令代码,常量,对象的引用地址

1,  保存对象实例,实际上保存的是对象实例的属性值,属性类型和对象本身的标记等,并不保存对象的方法(方法是指令,保存在stack中)。

对象实例在heap中分配好以后,需要在stack中保存一个4个字节的内存地址,用来定位该对象实例的在heap中的位置,以便于找到该对象的实例。

2,  基本数据类型包括,byte,short,int,long,double,float,char,Boolean.

 

 

Java的堆是一个运行时的数据区,类的对象从中分配空间。这些对象通过new,newarray,anewarray和multinewarray等指令建立,它们不需要程序代码来显示的释放。Heap是用垃圾回收器负责回收的,堆得优势可以动态的分配内存大小,生存期不必事先告诉编译器,因为他是运行时动态分配内存的,java的垃圾回收器会自动回收掉这些不再使用的数据。但缺点是由于是运行时动态分配,所以速度相对较慢。

 

栈的优势是,存取速度比堆要快,仅次于寄存器,数据可以共享。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。栈中主要存放一些基本的数据类型,byte,short,int,long,double,float,char,Boolean和对象句柄即应用。

 

可见垃圾回收器是针对堆得,而栈本身是first in,last out 先进后出,能够自动释放,这样就明白到new创建的,都是放到堆里面的。

 

 

在java里面参数传递都是按值传递  这句话的意思是:按值传递是传递的值得拷贝,按引用传递其实传递的是引用的地址值,所以统称按值传递。


下面举三个例子

public class Test1 { public static void main(String[] args) { int n = 3; System.out.println("Before change, n = " + n); changeData(n); System.out.println("After changeData(n), n = " + n); } public static void changeData(int nn) { n = 10; }}


我想这个例子大家都明白,基本类型作为参数传递时,是传递值的拷贝,无论你怎么改变这个拷贝,原值是不会改变的,输出的结果证明了这一点:

Before change,n = 3

AfterchangeData(n), n = 3

我理解更简单,main函数中的实例n跟changeData函数中的形参是两个的实例,第一次打印的是实例n的原始值,第二次打印的依然是n的原始值,但是如果你在changeData中打印形参n的值却是10,改变的是main中实例n的原始值的拷贝。这样其实更有助于理解。


public class Test2 {       public static void main(String[] args) {        StringBuffer sb = new StringBuffer("Hello ");        System.out.println("Before change, sb = " + sb);        changeData(sb);        System.out.println("After changeData(n), sb = " + sb);    }                                                                                           public static void changeData(StringBuffer strBuf) {        strBuf.append("World!");    }}

先看输出结果:

Before change, sb = Hello

After changeData(n), sb = Hello World!

从结果来看,sb的值被改变了,那么是不是可以说:对象作为参数传递时,是把对象的引用传递过去,如果引用在方法内被改变了,那么原对象也跟着改变。从上面例子的输出结果来看,这样解释是合理。

现在我们对上面的例子稍加改动一下


public class Test3 {       public static void main(String[] args) {        StringBuffer sb = new StringBuffer("Hello ");        System.out.println("Before change, sb = " + sb);        changeData(sb);        System.out.println("After changeData(n), sb = " + sb);    }                                                                                                                                                                                                                                               public static void changeData(StringBuffer strBuf) {           strBuf = new StringBuffer("Hi ");           strBuf.append("World!");    }}

按照上面例子的经验:对象作为参数传递时,是把对象的引用传递过去,如果引用在方法内被改变了,那么原对象也跟着改变。你会认为应该输出:

Before change, sb = Hello

After changeData(n), sb = Hi World!

但运行一下这个程序,你会发现结果是这样的:

Before change, sb = Hello

After changeData(n), sb = Hello

这就是让人迷惑的地方,对象作为参数传递时,同样是在方法内改变了对象的值,为什么有的是改变了原对象的值,而有的并没有改变原对象的值呢?这时候究竟是“传值”还是“传引用”呢?

 

这两个方法是的实例是引用类型,引用类型中相对于基本类型要复杂一些。

Test2中main方法StringBuffer的实例sb是hello,sb是对象的引用。好的,那在changData中形参strBuf传参传的是引用sb,sb与strBuf共同持有一个引用,同时指向对象hello,因此在main和changeData中共同处理一个引用,进而改变了对象。

 

Test3中main方法StringBuffer的实例sb也是hello,但是在changeData中却new了一个对象strBuff,引用类型new对象肯定是在栈中重新开辟一块内存,而在堆里分配了hi这参数,因此在changeData中同理的又不是跟main中相同的引用。所以结果就是我们看到的。

 

当然string这个类型也比较特殊,对它的一些操作符是重载的,如:

String str = “Hello”; 等价于String str = new String(“Hello”);

String str = “Hello”;

str = str + “ world!”;等价于str = new String((newStringBuffer(str)).append(“ world!”));

 



0 0