对于引用变量的深层含义,未必在初学的时候就能深刻理解,
所以理解好下面这两句话的真正含义非常重要
Case cc=new Case();
Case cc;
cc=new Case();
1.先搞清楚什么是堆,什么是栈。
Java开辟了两类存储区域,对比二者的特点
存储区域 | 存储内容 | 优点 | 缺点 | 回收 |
---|---|---|---|---|
栈 | 基本类型的变量和对象的引用变量 | 存取速度比堆要快,仅次于寄存器,栈数据可以共享 | 存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。栈中主要存放一些基本类型的变量 | 当超过变量的作用域后,Java会自动释放掉该变量,内存空间可以立即被另作他用 |
堆 | 由new等指令创建的对象和数组 | 可以动态地分配内存大小,生存期也不必事先告诉编译器 | 由于要在运行时动态分配内存,存取速度较慢 | 由Java虚拟机的自动垃圾回收器来回收不再使用的数据 |
堆栈的存储特点决定了其中存储的数据类型。
注意,栈内存储的除了基本类型的变量(float, int 这种类型的变量)还会存储对象的引用变量。java中,引用变量实际上是一个指针,它指向的是堆内存中对象实例。
引用变量就相当于是为数组或对象起的一个名称,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象。
2.给引用变量赋值
回过头再来看代码
实际上里面分解成了四个步骤。
Case cc; '''在栈内存里面开辟了空间给引用变量cc,这时cc=null'''
cc=new Case();
'''
1. new Case()在堆内存里面开辟了空间给Case类的对象,这个对象没有名字
2. Case()随即调用了Case类的构造函数
3. 把对象的地址在堆内存的地址给引用变量cc
'''
这样我们就明确了:
为了形象地说明对象、引用及它们之间的关系,可以做一个或许不很妥当的比喻。对象好比是一只很大的气球,大到我们抓不住它。引用变量是一根绳, 可以用来系汽球
紧接着就会问,引用变量是怎么传递的呢?
这就涉及到Java唯一的参数传递方式——按值传递
看下面一段代码:
public class ObjectRef {
'''基本类型的参数传递'''
public static void testBasicType(int m) {
System.out.println("m=" + m);//m=50
m = 100;
System.out.println("m=" + m);//m=100
}
'''参数为对象,不改变引用的值'''
'''s即sMain指向的对象执行了append方法,在原来的字符串上加了段“_add”'''
public static void add(StringBuffer s) {
s.append("_add");
}
'''参数为对象,改变引用的值 '''
'''引用变量指向了一个新的对象,已经不是sMain指向的对象了'''
public static void changeRef(StringBuffer s) {
s = new StringBuffer("Java");
System.out.println("sMain=" + sMain.toString());'''sMain=Java'''
}
public static void main(String[] args) {
int i = 50;
testBasicType(i);
System.out.println(i);'''i=50'''
StringBuffer sMain = new StringBuffer("init");
System.out.println("sMain=" + sMain.toString());'''sMain=init'''
add(sMain);
System.out.println("sMain=" + sMain.toString());'''sMain=init_add'''
changeRef(sMain);
System.out.println("sMain=" + sMain.toString());'''sMain=init_add'''
}
}
看这里,给人的感觉是传递过来的明明是对象的引用,为什么就是值得传递呢?
因为传递之前,被传的就是个引用啊,我们所谓的“传地址”,在传之前,那可是一个实例,传过来的是实例的地址。这里传递的值,从始至终就是个地址,sMain就是个地址,传给s还是个地址。你们感受下:
'''参数为对象,不改变引用的值'''
'''s即sMain,指向的对象执行了append方法,在原来的字符串上加了段“_add”'''
public static void add(StringBuffer s) {
s.append("_add");
}
以上输出的结果会是“init_add”
而这里,s引用了一个新的对象,根本没有进行参数的传递,它和之前的sMain没有关系了。
'''参数为对象,改变引用的值 '''
'''引用变量指向了一个新的对象,已经不是sMain指向的对象了'''
public static void changeRef(StringBuffer s) {
s = new StringBuffer("Java");
System.out.println("sMain=" + sMain.toString());'''s=Java'''
}
以上输出的结果会是“Java”
.
引用《Java编程思想》中的一段话:
倘若“将一个对象赋值给另一个对象”,实际是将“引用”从一个地方复制到另一个地方:
且慢,文章初稿发表后,没有仔细检查,大家看评论就会发现,我当时直接在main方法里打印了changRef的结果,发现并不是我说的“sMain=Java”,仍然还是“sMain=init_add’”。给大家道个歉。
接下来解释下为什么会这样,涉及到两点:作用域和Java的回收机制
作用域大家都觉得容易,不就是作用在自己所在的花括号里嘛?
{
int x = 12;
/* only x available */
{
int q = 96;
/* both x & q available */
}
/* only x available */
/* q “out of scope” */
}
这只是作用域的一方面“变量的可见性”,另一方面,作用域决定了变量何时被回收,也就是变量的“存在时间”
再回到我一开始给大家列出来的表,看一下“回收”那一列:
存储区域 | 存储内容 | 优点 | 缺点 | 回收 |
---|---|---|---|---|
栈 | 基本类型的变量和对象的引用变量 | 存取速度比堆要快,仅次于寄存器,栈数据可以共享 | 存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。栈中主要存放一些基本类型的变量 | 当超过变量的作用域后,Java会自动释放掉该变量,内存空间可以立即被另作他用 |
堆 | 由new等指令创建的对象和数组 | 可以动态地分配内存大小,生存期也不必事先告诉编译器 | 由于要在运行时动态分配内存,存取速度较慢 | 由Java虚拟机的自动垃圾回收器来回收不再使用的数据 |
对于基本类型的变量和引用变量,离开所在的作用域后直接被JVM垃圾回收
对于用new关键字创建一个Java对象的时候,它会超出作用域的范围之外。
public static void changeRef(StringBuffer s) {
s = new StringBuffer("Java");
System.out.println("sMain=" + sMain.toString());'''sMain=Java'''
} '''这里是作用域终点'''
也就是在作用域终点处,引用变量已经被回收了,但对象实例还存在。对象实例何时被回收,由JVM的垃圾回收机制,我们不关心。
.
因此到了main方法,已经超出了引用变量s的作用域,但还是在引用变量sMain的作用域,因此,打印出来的结果,还是main方法中创建的对象的toString()结果。上一句因为对该对象执行了append()方法,因此结果就是’’‘sMain=init_add’’’
.
可能还有一点疑问,为什么这里的结果貌似没有收到作用域的影响呢
add(sMain);
System.out.println("sMain=" + sMain.toString());'''sMain=init_add'''
因为在add()方法里,传入对象的引用后,直接对传入的对象调用了方法。add()方法执行完,确实传入的引用变量s被回收了,但原来被引用的对象字符串表示已经被改变了。
在changeRef()方法里,传入对象的引用后,只是让它引用了别的对象,并没有对原来的对象直接进行操作。changeRef()方法执行完,引用变量s被回收,原来被引用的对象没有发生任何变化。
.
因此要想看到返回的对象的字符串是“Java”,那我们只能去指向这个对象的引用变量所在的作用域去打印它:
public static void changeRef(StringBuffer s) {
s = new StringBuffer("Java");
System.out.println("sMain=" + sMain.toString());'''s=Java'''
}
.
.
.
.
.
参考文章:
Java对象及其引用:http://blog.sina.com.cn/s/blog_4cd5d2bb0100ve9r.html
java堆栈详解 :http://blog.csdn.net/bangbt/article/details/6232299
Java变量作用域详解:https://www.cnblogs.com/CodingAndRiding/p/7456347.html