这篇博文主要介绍Java 对象的引用方式,以及对于Java 程序设计语言中关于”引用传递”与”值传递”问题谈一谈看法。
Java对象的访问定位
我们知道Java 程序需要通过栈上的引用(reference) 数据来操作堆上的具体对象。由于reference 类型在Java 虚拟机规范中只规定了一个指向对象的引用,并没有定义这个引用通过何种方式去定位、访问堆中对象的具体位置,所以对象的访问方式也是取决于虚拟机而定的。但是主流的访问方式有句柄和直接指针两种。下面一一分作介绍。
句柄访问
使用句柄方式访问的话,在Java 堆中将会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息。如图:
直接指针
如果使用直接指针访问,那么Java 堆中对象的布局就必须考虑如何放置访问类型数据的想关信息,而reference 中存储的直接就是对象的地址。如图:
两种方式的对比:
使用句柄的最大好处就是reference 中存储的是稳定的句柄地址,在对象移动(在垃圾回收时往往存在大量的对象移动)时只会改变句柄中的实例数据指针,而reference 本身不需要修改。
使用直接指针的最大好处时速度更快,它节省了一次指针定位的开销,由于对象的访问在Java 中非常普遍,因此这类开销积少成多后也是一项非常可观的执行成本。就Sun HotSpot 虚拟机而言,它是使用第二种方式进行对象访问的。
下面就来谈一谈Java 中关于“引用传递”与“值传递”的问题:
在探讨这个问题之前我们先了解一下什么是值传递什么是引用传递(来自百度百科):
引用传递:所谓引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。
值传递:值传递是在程序设计中,对于函数调用的一种方法,值引用只是把值传递到新的变量,修改新的变量,不会修改原来的参数。
通过上面关于Java 对象的两种访问方式我们知道,在虚拟机栈中通过对象的引用来操作堆上的具体对象。下面来看两段代码:
@Test
public void test1(){
int i = 5;
change1(i);
System.out.print(i);
}
public void change1(int i){
i = 10;
}
我想大多数Java 程序员都是到输出的结果是5,还有下面一段代码:
@Test
public void test2(){
Demo demo = new Demo();
System.out.println(demo.num);
change2(demo);
System.out.println(demo.num);
}
public void change2(Demo demo){
demo.num = 10;
}
class Demo{
public int num = 5;
}
输出结果为:
5
10
上面两个例子分别测试了基本数据类型与引用数据类型,现在就上面出现的结果做一些理论上的分析:
基本数据类型的传递:基本数据类型的值就保存在变量中,传递的是基本类型的字面量值的拷贝,当发生传递时并不会改变原来的变量值。
引用数据类型的传递:变量中保存的是实际对象的地址,传递的是堆中对象的一个拷贝(也就是引用),操作的并不是堆中的对象本身,在数据传递时原来的引用地址会被覆盖,赋值运算会改变原来引用中保存的地址,但是堆中的对象本身不会被改变。
总结:
“Java 所支持的‘按址传递’传递是完全错误的”,因为Java 对象标志符实际上是“符号引用”。也许有人会赞成这种精确却令人费解的解释,但我认为我的这种方法可以简化概念上的理解并且不会伤害到任何事物。(好了,那些言语专家可能会说我在说谎,但是我认为我只是提供了一个合适的抽象罢了。
->《Java编程思想》
参考资料:《深入理解Java 虚拟机》周志明 著
《Java 编程思想》Bruce Eckel 著
知乎平台->手动@ Intopass、祖春雷
百度百科