今天查阅了很多资料,理解是五花八门,看过这些文章之后我也把C++,C#,java中引用类型的问题彻底搞晕了(C++与C#中用指针概念来理解比较容易,然而java中不存在指针概念某些文章还冠以指针思想来理解,使得我思维很混乱),对于栈与堆的存储机制也彻底迷茫了。于是我决定还是依照自己的理解来总结下吧,本身理解并不到位,文章必会有所偏差,还望大家指正,谢谢。
正题开始之前我想说明一点,值类型与引用类型概念看似简单,但是却没有人能很好的形象的表述,这个概念对于程序员而言很基础很重要,希望我能和大家一起慢慢累积经验然后升华对此的理解。闲话至此。
一,基本概念:学习任何知识首先要知道它的基本概念,对于这两个概念书本上是这么定义的。
1, 值类型:存放在栈内的一个变量中。即是在栈中分配内存空间,直接存储所包含的值,其值就代表数据本身。值类型的数据具有较快的存取速度。 包含基本数据类型。(int,bool,double,float等)
2, 引用类型:存储于堆中。即是在堆中分配内存空间,栈中所存储的引用不直接存储所包含的值,而是指向所要存储的值,其值代表的是所指向的地址。当访问一个具有引用类型的数据时,需要到栈中检查变量的内容,该变量引用堆中的一个实际数据。引用类型的数据比值类型的数据具有更大的存储规模和较低的访问速度。 包含:object和string。
个人认为这两个定义总结的很精辟,短短几句话就将两种类型的本质与在内存中的存储机制表述的很完整,唯一的缺点就是不好理解,所以下面我需要慢慢来理解。对于这两个值在内存中的存储,我认为用图示表达会更直观。
参照图示我们执行以下代码:
Public void method(){
int a=100;
String s=new String();
}
当代码开始执行到第一行时便为我们定义的i在栈中分配了一块内存区域,同时将i的值100储存进去,这便是值类型,比较简单。然后代码继续运行至第二行,代码仅很短看似很简单可编译器却做了很多工作,首先创建了一个String类型的对象s,此时编译器在栈上分配了一块区域用以存放变量的地址,(很多文章中说这里是创建了一个指针,在C++和C#中这样便于理解,但是java中并没有指针的概念,所以我们就说分配了一块区域用于存放变量的地址,其实目的都是一样的,因为指针就是指向变量首地址的)此时便可以看出值类型与引用类型的不同了,执行到现在引用类型并没有存储任何值,只是储存了变量的地址,真正的对象是储存在堆内存上的,如上图所示,也就是说声明String s时并不会给String的实例分配内存,而是分配一个栈变量s(并设置为null),然后把它指向“堆”。最后进行的便是‘=’的赋值操作了,也就是把刚申请到的内存的地址保存为s的值。(这段话不知道有没有把大家绕晕,我写的有点晕,多体会两遍理解起来应该没有太大的问题。)
对于概念有了大致的了解之后继续后面的问题。
二,赋值操作:
1, 值类型变量的赋值,如下例:
Public void method(){
int a=100;
int b=a;
}
这是一个很简单的值类型变量的赋值操作,很容易看出,b=100。当我们把一个int值分配给另外一个int值时,需要创建一个完全不同的拷贝。换句话说,你可以改变其中任何一个而不会影响另外一个。这种数据类型被称为值类型。此时又深入的理解了值类型的概念,为了使得理解更透彻我们用图示再来演示下。
结果一目了然。
2, 引用类型变量赋值,如下例:
Public void method(){
String s=new String();
String s2=s;
}
如我们之前所知,我们创建对象的同时分配了一块栈内存用于存放变量的地址,注意仅是存放的地址而并非真实的值,所以当代码执行到第二行时就在栈中多了一块存放变量地址的区域,赋值操作使得两块区域同时指向相同的堆内存,所以当我们改变期中一个的时候另一个也会随之改变,这种数据类型即为引用类型,同样以图示来演示引用类型变量的赋值。
这样就比较直观了。
上面的内容相对而言就比较好理解,使我最最头痛的是接下来的问题。
三,引用类型在函数调用中的参数传递:网上针对引用类型传递的参数到底是传值还是传引用的说法各不相同,有说是传值,有说是传址,还有的说根据不同的类型有不同的传递方法,学识浅薄的我真的不知道孰对孰错,以前学习C++时有指针的概念,于是对于参数传递便能轻松的知道,java里面没有了指针概念少去了好多麻烦是肯定,然而在这个问题的理解上却有了些许不方便,所以下面我就稍稍表达下自己的理解吧,可能并不正确,也是希望大家能指正,千万不要看我犯错而置之不理啊。
其实我觉得啊,这个到底是传值还是传引用在于自己怎么理解,比如用以前举过的例子吧,
Public void method(){
int a=100;
String s=new String();
}
现在若给出这样一个语句System.out.println(s);则可以看做是传引用方式,将引用的首地址传递进来,编译器开始编译就对栈进行查找,找到所对应传递的地址后指向对内存中存储的对象。
但是若给出语句System.out.println(a);则毋庸置疑是传值。
所以我的理解便是要根据参数的不同类型加以区别。
实在抱歉我不能对此有更好的理解,所以在此贴出几个网址,里面有关于这个问题的讨论,大家可以进行参阅,我也将继续研习这个问题,希望能在不久之后有更好的理解。
http://topic.youkuaiyun.com/t/20060322/19/4632866.html
四,最后的最后来总结下值类型与引用类型的区别:(这个部分借鉴了网上高手的总结,在此表示感谢。)
1、值类型通常被分配在栈上,它的变量直接包含变量的实例,使用效率比较高。
2、引用类型分配在托管堆上,引用类型的变量通常包含一个指向实例的指针,变量通过该指针来引用实例。
3、值类型继承自ValueType(注意:而System.ValueType又继承自System.Object);而引用类型继承自System.Object。
4、值类型变量包含其实例数据,每个变量保存了其本身的数据拷贝(副本),因此在默认情况下,值类型的参数传递不会影响参数本身;而引用类型变量保存了其数据的引用地址,因此以引用方式进行参数传递时会影响到参数本身,因为两个变量会引用了内存中的同一块地址。
5、值类型有两种表示:装箱与拆箱;引用类型只有装箱一种形式。我会在下节以专门的篇幅来深入讨论这个话题。
6、典型的值类型为:struct,enum以及大量的内置值类型;而能称为类的都可以说是引用类型。
7、值类型的内存不由GC(垃圾回收,Gabage Collection)控制,作用域结束时,值类型会自行释放,减少了托管堆的压力,因此具有性能上的优势。例如,通常struct比class更高效;而引用类型的内存回收,由GC来完成,微软甚至建议用户最好不要自行释放内存。
8、值类型是密封的(sealed),因此值类型不能作为其他任何类型的基类,但是可以单继承或者多继承接口;而引用类型一般都有继承性。
9、值类型不具有多态性;而引用类型有多态性。
10、值类型变量不可为null值,值类型都会自行初始化为0值;而引用类型变量默认情况下,创建为null值,表示没有指向任何托管堆的引用地址。对值为null的引用类型的任何操作,都会抛出NullReferenceException异常。
11、值类型有两种状态:装箱和未装箱,运行库提供了所有值类型的已装箱形式;而引用类型通常只有一种形式:装箱。
关于这个内容到此结束,写的时候感觉杂乱无章,错误百出,希望大家批评指正。