7,对象的初始化以及实例变量的作用域
本系列讲座有着很强的前后相关性,如果你是第一次阅读本篇文章,为了更好的理解本章内容,笔者建议你最好从本系列讲座的第1章开始阅读,请点击这里 。
到目前为止,我们都使用的是下列方式创建对象
这种new的方式,实际上是一种简化的方式。笔者在这里总结一下前面几章里面曾经提到过关于创建对象的2个步骤:
第一步是为对象分配内存也就是我们所说的allocation,runtime会根据我们创建的类的信息来决定为对象分配多少内存。类的信息都保存 在Class里面,runtime读取Class的信息,知道了各个实例变量的类型,大小,以及他们的在内存里面的位置偏移,就会很容易的计算出需要的内 存的大小。分配内存完成之后,实际上对象里面的isa也就被初始化了,isa指向这个类的Class。类里面的各个实例变量,包括他们的超类里面的实例变 量的值都设定为零。
需要注意的是,分配内存的时候,不需要给方法分配内存的,在程序模块整体执行的时候方法部分就作为代码段的内容被放到了内存当中。对象的内容被放到了数据段当中,编译好的方法的汇编代码被放到了代码段当中。在Objective C里面,分配内存使用下列格式:
NSObject已经为我们提供了诸如计算内存空间大小以及初始化isa还有把各个实例变量清零,毫无疑问NSObject已经非常出色的完成了内存分配的工作,在一般情况下,我们不需要重写alloc方法。
第 二步是要对内存进行初始化也就是我们所说的Initialization。 初始化指的是对实例变量的初始化。虽然在alloc方法里面已经把各个实例变量给清零了,但是在很多情况下,我们的实例变量不能是零(对于指针的实例变量 而言,就是空指针)的,这样就需要我们对实例变量进行有意义的初始化。
按照Objective-C的约定,当初始化的时候不需要参数的话,就直接使用init方法来初始化:
init是一个定义在NSObject里面的一个方法,NSObject明显无法预测到派生类的实例变量是什么,所以同学们在自己的类里面需要重载一下init方法,在init方法里面把实例变量进行初始化。
但 是,需要强调的是,由于某种原因我们的init也许失败了,比如说我们需要读取CNBLOGS.COM的某个RSS,用这个RSS来初始化我们的对象,但 是由于用户的网络连接失败所以我们的init也许会失败,在手机应用当中的一些极端的情况下比如说有同学写一个读取网页内容的程序,在网页内容非常大的时 候,那么alloc也有可能会失败,为了可以方便的捕获这些失败,所以我们在程序当中需要把上面的过程写在一起:
if (对象名)
else
加上了上面的if语句我们的初始化过程就是完美的,当然我们有的时候不需要这个if语句。当我们的alloc和init永远不会失败的时候。关于初始化的时候的错误捕获,笔者将在后面的章节里面论述。
为了我们写程序方便和简洁,在创建一个从NSObject派生的类的对象的时候,苹果公司把alloc和init简化成为new,我们在程序代码当中使用任何一种方式都是可以的,具体怎么写是同学们的喜好和自由。
到这里,有同学会问,如果我们的init需要参数怎么办?按照Objective-C的约定,我们需要使用initWith...。也就是带参数的变量初始化,这个也是本章的主要内容。
本章在讲述initWith的同时,也将会顺便的给大家介绍一下实例变量的作用域。
7.1,本章程序的执行结果
在 本章里面,我们将要继续使用我们在第4章已经构筑好的类Cattle和Bull。从一般的面向对象的角度上来说,是不鼓励我们改写已经生效的代码的。但是 本章的目的是为了使同学们可以很好的理解主题,所以笔者在这里暂时违反一下规则改写了一下Cattle类,在里面追加了initWith方法,笔者也在 Cattle类里面追加了一些实例变量为了阐述实例变量的作用域的问题。由于在Cattle类里面笔者追加了一些东西,所以在Bull类里面改写了 saySomething这个函数,让我们的Bull可以说更多的内容。我们的redBull是这样说的:
图7-1,本章程序的执行结果
本章程序代码晴点击这里 下载。
再次强调 在实际的编程过程中,尤其是写大型程序多人合作的时候,除非发现BUG,否则不要改写已经生效的代码。这样会产生一些意想不到的结果,从而使其他的弟兄们或者姐妹们对你充满怨言。
7.2,实现步骤
第一步,按照我们在第2章所述的方法,新建一个项目,项目的名字叫做07-InitWithAndIvarScope。如果你是第一次看本篇文章,请到这里参看第二章的内容。
第二步,按照我们在第4章的4.2节的第二,三,四步所述的方法,把在第4章已经使用过的“Cattle.h”,“Cattle.m”,“Bull.h”还有“Bull.m”, 导入本章的项目里面。然后把第6章里面的“MyNSObject.h”也导入到项目当中。
第三步,打开“Cattle.h”,修改成为下面的代码并且保存:
#import <Foundation/Foundation.h> @interface Cattle : NSObject { int legsCount; @private bool gender; //male = YES female = NO @protected int eyesCount; @public NSString *masterName; } - (void)saySomething; - (void)setLegsCount:(int) count; - (id)initWithLegsCount:(int) theLegsCount gender:(bool) theGender eyesCount:(int) theEyesCount masterName:(NSString*)theMasterName; @end
第4步,打开“Cattle.m”,修改成下面的代码并且保存:
#import "Cattle.h" @implementation Cattle -(void) saySomething { NSLog(@"Hello, I am a cattle, I have %d legs.", legsCount); } -(void) setLegsCount:(int) count { legsCount = count; } -(id)init { [super init]; return [self initWithLegsCount:4 gender:YES eyesCount:2 masterName:@"somebody"]; } - (id)initWithLegsCount:(int) theLegsCount gender:(bool) theGender eyesCount:(int) theEyesCount masterName:(NSString*)theMasterName { legsCount = theLegsCount; gender = theGender; eyesCount = theEyesCount; masterName = theMasterName; return self; } @end
第五步,打开“Bull.m”, ,修改成下面的代码并且保存:
#import "Bull.h" @implementation Bull -(void) saySomething { NSLog(@"Hello, I am a %@ bull, I have %d legs.", [self getSkinColor],legsCount); NSLog(@"I have %d eyes, my master is %@.", eyesCount,masterName); //List below is illegal //NSLog(@"My gender is %@",gender ? @"male" : @"female"); } -(NSString*) getSkinColor { return skinColor; } - (void) setSkinColor:(NSString *) color { skinColor = color; } @end
第六步,打开“07-InitWithAndIvarScope.m”,修改成下面的代码并且保存:
#import <Foundation/Foundation.h> #import "Bull.h" #import "Cattle.h" #import "MyNSObject.h" int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; Bull *redBull = [[Bull alloc] initWithLegsCount:4 gender:YES eyesCount:2 masterName:@"that cowboy"]; [redBull setSkinColor:@"red"]; [redBull saySomething]; //legal, but not good redBull->masterName = @"that cowgirl"; //legal, but bad //redBull->eyesCount = 3; //Trying to access a private ivar, VERY bad thing //MyClass bullClass = redBull->isa; bool *redBullGender = (bool *)(redBull) + 8; NSLog(@"My gender is %@",*redBullGender ? @"male" : @"female"); [pool drain]; return 0; }
第 七步,选择屏幕上方菜单里面的“Run”,然后选择“Console”,打开了Console对话框之后,选择对话框上部中央的“Build and Go”,如果不出什么意外的话,那么应该出现入图7-1所示的结果。如果出现了什么意外导致错误的话,那么请仔细检查一下你的代码。如果经过仔细检查发现 还是不能执行的话,可以到这里 下载笔者为同学们准备的代码。 如果笔者的代码还是不能执行的话,请告知笔者。
7.3,实例变量的作用域(Scope)
对于Objective-C里面的类的实例变量而言,在编译器的范围里面,是有作用域的。和其他的语言一样,Objective-C也支持public,private还有protected作用域限定。
如果一个实例变量没有任何的作用域限定的话,那么缺省就是protected。
如果一个实例变量适用于public作用域限定,那么这个实例变量对于这个类的派生类,还有类外的访问都是允许的。
如果一个实例变量适用于private作用域限定,那么仅仅在这个类里面才可以访问这个变量。
如果一个实例变量适用于protected作用域限定,那么在这个类里面和这个类的派生类里面可以访问这个变量,在类外的访问是不推荐的。
我们来看看“Cattle.h”的代码片断:
int legsCount; @private bool gender; //male = YES female = NO @protected int eyesCount; @public NSString *masterName;
第一行的legsCount 的前面没有任何作用域限定,那么它就是protected的。
第二行是在说从第二行开始的实例变量的定义为private的,和其他的关键字一样,Objective-C使用@来进行编译导向。
第三行的gender的作用域限定是private的,所以它适用于private作用域限定。
第四行是在说从第四行开始的实例变量的定义为protected的,同时第二行的private的声明作废。
第五行的eyesCount的作用域限定是protected的,所以它适用于protected作用域限定。
第六行是再说从第六行开始的实例变量的定义为public的,同时第四行的protected的声明作废。
第七行的masterName的作用域限定是public的,所以它适用于public作用域限定。
我们再来看看在派生类当中,private,protected还有public的表现。Bull类继承了Cattle类,笔者改写了一下“Bull.m”用来说明作用域的问题,请参看下面的代码:
-(void) saySomething { NSLog(@"Hello, I am a %@ bull, I have %d legs.", [self getSkinColor],legsCount); NSLog(@"I have %d eyes, my master is %@.", eyesCount,masterName); //List below is illegal //NSLog(@"My gender is %@",gender ? @"male" : @"female"); }
我们来看看第3还有第4行代码,我们可以访问legsCount,eyesCount还有masterName。
在第6行代码当中,我们试图访问gender这个Cattle的私有(private)属性,这行代码产生了编译错误,所以我们不得不注释掉第6行代码。
好的,我们再来看看类的外部private,protected还有public的表现。请同学们打开“07-InitWithAndIvarScope.m”,参考一下下面的代码:
//legal, but not good redBull->masterName = @"that cowgirl"; //legal, but bad //redBull->eyesCount = 3; //Trying to access a private ivar, VERY bad thing //MyClass bullClass = redBull->isa; bool *redBullGender = (bool *)(redBull) + 8; NSLog(@"My gender is %@",*redBullGender ? @"male" : @"female");
在 第二行里面,我们访问了masterName,由于在Cattle里面masterName是public的,Bull继承了Cattle,所以我们可以 直接访问masterName。但是这不是一种好的习惯,因为这不符合面向对象的基本思想。实际上,如果没有特殊的理由,我们不需要使用public的。
第四行,我们试图在类的外边访问protected变量eyesCount,在这里笔者的Xcode只是轻轻的给了一个警告,编译成功并且可以运行。同样,这种在类的外边访问类的protected变量是一个很糟糕的做法。
我 们还记得在Bull的saySomething里面我们曾经试图访问过gender,但是编译器无情的阻止了我们,因为gender是私有的。但是,这仅 仅是编译器阻止了我们,当我们有足够的理由需要在类的外边访问private实例变量的时候,我们还是可以通过一些强硬的方法合法的访问私有变量的,我们 的方法就是使用指针偏移。
我们首先回忆一下第6章 的6.6节的内容,isa里面保存了对象里面的实例变量相对于对象首地址的偏移量,我们得到了这个偏移量之后就可以根据对象的地址来获得我们所需要的实例 变量的地址。在正常情况下,我们需要通过访问类本身和它的超类的ivars来获得偏移量的,但是笔者在这里偷了一个懒,先使用第七行的代码MyClass bullClass = redBull->isa;通过Debugger获得gender的偏移量,数值为8。然后在第8行里面,笔者通过使用指针偏移取得了gender 的指针然后在第9行实现了输出。
由此可见,在Objective-C里面, 所谓的private还有protected只是一个Objective-C强烈推荐的一个规则,我们需要按照这个规则来编写代码,但是如果我们违反了这个规则,编译器没有任何方法阻止我们。
笔者认为在类的外部直接访问任何实例变量,不管这个实例变量是public,private还是protected都是一个糟糕的做法,这样会明显的破坏封装的效果,尽管这样对编译器来说是合法的。
7.4,initWith...
NSObject为我们准备的不带任何参数的init,我们的类里面没有实例变量,或者实例变量可以都是零的时候,我们可以使用NSObject为我们准 备的缺省的init。当我们的实例变量不能为零,并且这些实例变量的初始值可以在类的初始化的时候就可以确定的话,我们可以重写init,并且在里面为实 例变量初始化。
但是在很多时候, 我们无法预测类的初始化的时候的实例变量的初始值,同时NSObject明显无法预测到我们需要什么样的初始值,所以我们需要自己初始化类的实例变量。
请同学们打开“Cattle.m”,我们参考一下下面的代码:
-(id)init { [super init]; return [self initWithLegsCount:4 gender:YES eyesCount:2 masterName:@"somebody"]; } - (id)initWithLegsCount:(int) theLegsCount gender:(bool) theGender eyesCount:(int) theEyesCount masterName:(NSString*)theMasterName { legsCount = theLegsCount; gender = theGender; eyesCount = theEyesCount; masterName = theMasterName; return self; }
从 第3行到第7行,笔者重写了一下init。在init里面,笔者给通过调用initWith,给类的各个实例变量加上了初始值。这样写是很必要的,因为将 来的某个时候,也许有人(或者是自己)很冒失的使用init来初始化对象,然后就尝试使用这个对象,如果我们没有重写init,那么也许会出现一些意想不 到的事情。
从第9行到第19行,是我们自己定义的initWith,代码比较简单,笔者就不在这里赘述了。需要注意的一点是,笔者没有在这里调用[super init]; 。原因是Cattle的超类就是NSObject,初始化的过程就是初始化实例变量的过程,runtime已经为我们初始化好了NSObject的唯一实例变量isa,也就是Cattle的类的信息,所以我们不需要调用[super init]; 。在某些时候,超类的变量需要初始化的时候,请同学们在子类的init或者initWith里面调用[super init]; 。
请同学们再次打开“07-InitWithAndIvarScope.m”,参考下面的代码片断:
Bull *redBull = [[Bull alloc] initWithLegsCount:4 gender:YES eyesCount:2 masterName:@"that cowboy"]; [redBull setSkinColor:@"red"]; [redBull saySomething];
从第1行到第4行就是调用的initWith来初始化我们的redBull。
7.5,本章总结
非常感谢大家对笔者的支持!
我 们在本章里面介绍了2个比较轻松的话题,一个是实例变量的作用域,这个概念笔者个人认为对有一点面向对象编程经验的人来说,不是什么新鲜的概念了。但是需 要注意的是,Objective-C并没有强制我们遵守它的规则,他仍旧为我们提供了违反规则的机会,这一点上根C++比较类似。只要支持指针,就无法避 免使用者违反规则。事务都是一分为二的,当我们得到了访问任何变量的自由之后,我们必须为访问这些变量承担后果。
第二个话题就是initWith。和其他的面向对象的语言不同,Objective-C没有构造函数,它通过init还有initWith来初始化变量, 我们应该根据具体情况进行具体的分析,从而编写我们的init还有initWith方法。
来源: http://www.cnblogs.com/yaski/archive/2009/04/19/1439304.html
Collection
├List
│├LinkedList
│├ArrayList
│└Vector
│ └Stack
└Set
Map
├Hashtable
├HashMap
└WeakHashMap
Collection接口
Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素(Elements)。一些 Collection允许相同的元素而另一些不行。一些能排序而另一些不行。Java SDK不提供直接继承自Collection的类,Java SDK提供的类都是继承自Collection的“子接口”如List和Set。
所有实现Collection接口的类都必须提供两个标准的构造函数:无参数的构造函数用于创建一个空的Collection,有一个 Collection参数的构造函数用于创建一个新的Collection,这个新的Collection与传入的Collection有相同的元素。后一个构造函数允许用户复制一个Collection。
如何遍历Collection中的每一个元素?不论Collection的实际类型如何,它都支持一个iterator()的方法,该方法返回一个迭代子,使用该迭代子即可逐一访问Collection中每一个元素。典型的用法如下:
Iterator it = collection.iterator(); // 获得一个迭代子
while(it.hasNext()) {
Object obj = it.next(); // 得到下一个元素
}
由Collection接口派生的两个接口是List和Set。
List接口
List是有序的Collection,使用此接口能够精确的控制每个元素插入的位置。用户能够使用索引(元素在List中的位置,类似于数组下标)来访问List中的元素,这类似于Java的数组。
和下面要提到的Set不同,List允许有相同的元素。
除了具有Collection接口必备的iterator()方法外,List还提供一个listIterator()方法,返回一个 ListIterator接口,和标准的Iterator接口相比,ListIterator多了一些add()之类的方法,允许添加,删除,设定元素,还能向前或向后遍历。
实现List接口的常用类有LinkedList,ArrayList,Vector和Stack。
LinkedList类
LinkedList实现了List接口,允许null元素。此外LinkedList提供额外的get,remove,insert方法在 LinkedList的首部或尾部。这些操作使LinkedList可被用作堆栈(stack),队列(queue)或双向队列(deque)。
注意LinkedList没有同步方法。如果多个线程同时访问一个List,则必须自己实现访问同步。一种解决方法是在创建List时构造一个同步的List:
List list = Collections.synchronizedList(new LinkedList(...));
ArrayList类
ArrayList实现了可变大小的数组。它允许所有元素,包括null。ArrayList没有同步。
size,isEmpty,get,set方法运行时间为常数。但是add方法开销为分摊的常数,添加n个元素需要O(n)的时间。其他的方法运行时间为线性。
每个ArrayList实例都有一个容量(Capacity),即用于存储元素的数组的大小。这个容量可随着不断添加新元素而自动增加,但是增长算法并没有定义。当需要插入大量元素时,在插入前可以调用ensureCapacity方法来增加ArrayList的容量以提高插入效率。
和LinkedList一样,ArrayList也是非同步的(unsynchronized)。
Vector类
Vector非常类似ArrayList,但是Vector是同步的。由Vector创建的Iterator,虽然和ArrayList创建的 Iterator是同一接口,但是,因为Vector是同步的,当一个Iterator被创建而且正在被使用,另一个线程改变了Vector的状态(例如,添加或删除了一些元素),这时调用Iterator的方法时将抛出ConcurrentModificationException,因此必须捕获该异常。
Stack 类
Stack继承自Vector,实现一个后进先出的堆栈。Stack提供5个额外的方法使得Vector得以被当作堆栈使用。基本的push和pop 方法,还有peek方法得到栈顶的元素,empty方法测试堆栈是否为空,search方法检测一个元素在堆栈中的位置。Stack刚创建后是空栈。
Set接口
Set是一种不包含重复的元素的Collection,即任意的两个元素e1和e2都有e1.equals(e2)=false,Set最多有一个null元素。
很明显,Set的构造函数有一个约束条件,传入的Collection参数不能包含重复的元素。
请注意:必须小心操作可变对象(Mutable Object)。如果一个Set中的可变元素改变了自身状态导致Object.equals(Object)=true将导致一些问题。
Map接口
请注意,Map没有继承Collection接口,Map提供key到value的映射。一个Map中不能包含相同的key,每个key只能映射一个 value。Map接口提供3种集合的视图,Map的内容可以被当作一组key集合,一组value集合,或者一组key-value映射。
Hashtable类
Hashtable继承Map接口,实现一个key-value映射的哈希表。任何非空(non-null)的对象都可作为key或者value。
添加数据使用put(key, value),取出数据使用get(key),这两个基本操作的时间开销为常数。
Hashtable通过initial capacity和load factor两个参数调整性能。通常缺省的load factor 0.75较好地实现了时间和空间的均衡。增大load factor可以节省空间但相应的查找时间将增大,这会影响像get和put这样的操作。
使用Hashtable的简单示例如下,将1,2,3放到Hashtable中,他们的key分别是”one”,”two”,”three”:
Hashtable numbers = new Hashtable();
numbers.put(“one”, new Integer(1));
numbers.put(“two”, new Integer(2));
numbers.put(“three”, new Integer(3));
要取出一个数,比如2,用相应的key:
Integer n = (Integer)numbers.get(“two”);
System.out.println(“two = ” + n);
由于作为key的对象将通过计算其散列函数来确定与之对应的value的位置,因此任何作为key的对象都必须实现hashCode和equals方法。hashCode和equals方法继承自根类Object,如果你用自定义的类当作key的话,要相当小心,按照散列函数的定义,如果两个对象相同,即obj1.equals(obj2)=true,则它们的hashCode必须相同,但如果两个对象不同,则它们的hashCode不一定不同,如果两个不同对象的hashCode相同,这种现象称为冲突,冲突会导致操作哈希表的时间开销增大,所以尽量定义好的hashCode()方法,能加快哈希表的操作。
如果相同的对象有不同的hashCode,对哈希表的操作会出现意想不到的结果(期待的get方法返回null),要避免这种问题,只需要牢记一条:要同时复写equals方法和hashCode方法,而不要只写其中一个。
Hashtable是同步的。
HashMap类
HashMap和Hashtable类似,不同之处在于HashMap是非同步的,并且允许null,即null value和null key。,但是将HashMap视为Collection时(values()方法可返回Collection),其迭代子操作时间开销和HashMap 的容量成比例。因此,如果迭代操作的性能相当重要的话,不要将HashMap的初始化容量设得过高,或者load factor过低。
WeakHashMap类
WeakHashMap是一种改进的HashMap,它对key实行“弱引用”,如果一个key不再被外部所引用,那么该key可以被GC回收。
总结
如果涉及到堆栈,队列等操作,应该考虑用List,对于需要快速插入,删除元素,应该使用LinkedList,如果需要快速随机访问元素,应该使用ArrayList。
如果程序在单线程环境中,或者访问仅仅在一个线程中进行,考虑非同步的类,其效率较高,如果多个线程可能同时操作一个类,应该使用同步的类。
要特别注意对哈希表的操作,作为key的对象要正确复写equals和hashCode方法。
尽量返回接口而非实际的类型,如返回List而非ArrayList,这样如果以后需要将ArrayList换成LinkedList时,客户端代码不用改变。这就是针对抽象编程。
在Android的图片处理中,碰到的一个非常普遍的问题便是OOM错误 为此网上也有很多例子,而在之前的一篇转载里 提到了ListView中加载图片的ImageLoader,而其中有一处,使用到了名为SoftPreference的类 这是Java中的一个类 也就是所谓的软引用 在查询了相关的资料以后 会发现SoftPreference的特性,非常适合用来处理OOM引起的问题 下面是百度文库的一篇转载:
SoftReference、Weak Reference和PhantomRefrence分析和比较
本文将谈一下对SoftReference(软引用)、WeakReference(弱引用)和PhantomRefrence(虚引用)的理解,这三个类是对heap中java对象的应用,通过这个三个类可以和gc做简单的交互。
强引用:
除了上面提到的三个引用之外,还有一个引用,也就是最长用到的那就是强引用.例如:
Object o=new Object(); Object o1=o;
上面代码中第一句是在heap堆中创建新的Object对象通过o引用这个对象,第二句是通过o建立o1到new Object()这个heap堆中的对象的引用,这两个引用都是强引用.只要存在对heap中对象的引用,gc就不会收集该对象.如果通过如下代码:
o=null; o1=null;
如果显式地设置o和o1为null,或超出范围,则gc认为该对象不存在引用,这时就可以收集它了。可以收集并不等于就一会被收集,什么时候收集这要取决于gc的算法,这要就带来很多不确定性。例如你就想指定一个对象,希望下次gc运行时把它收集了,那就没办法了,有了其他的三种引用就可以做到了。其他三种引用在不妨碍gc收集的情况下,可以做简单的交互。
heap中对象有强可及对象、软可及对象、弱可及对象、虚可及对象和不可到达对象。应用的强弱顺序是强、软、弱、和虚。对于对象是属于哪种可及的对象,由他的最强的引用决定。如下:
String abc=new String("abc"); //1 SoftReference<String> abcSoftRef=new SoftReference<String>(abc); //2 WeakReference<String> abcWeakRef = new WeakReference<String>(abc); //3 abc=null; //4 abcSoftRef.clear();//5
上面的代码中:
第一行在heap对中创建内容为“abc”的对象,并建立abc到该对象的强引用,该对象是强可及的。
第二行和第三行分别建立对heap中对象的软引用和弱引用,此时heap中的对象仍是强可及的。
第四行之后heap中对象不再是强可及的,变成软可及的。同样第五行执行之后变成弱可及的。
SoftReference(软引用)
软引用是主要用于内存敏感的高速缓存。在jvm报告内存不足之前会清除所有的软引用,这样以来gc就有可能收集软可及的对象,可能解决内存吃紧问题,避免内存溢出。什么时候会被收集取决于gc的算法和gc运行时可用内存的大小。当gc决定要收集软引用是执行以下过程,以上面的abcSoftRef为例:
1 首先将abcSoftRef的referent设置为null,不再引用heap中的new String("abc")对象。
2 将heap中的new String("abc")对象设置为可结束的(finalizable)。
3 当heap中的new String("abc")对象的finalize()方法被运行而且该对象占用的内存被释放, abcSoftRef被添加到它的ReferenceQueue中。
注:对ReferenceQueue软引用和弱引用可以有可无,但是虚引用必须有,参见:
Reference(T paramT, ReferenceQueue<? super T>paramReferenceQueue)
被 Soft Reference 指到的对象,即使没有任何 Direct Reference,也不会被清除。一直要到 JVM 内存不足且 没有 Direct Reference 时才会清除,SoftReference 是用来设计 object-cache 之用的。如此一来 SoftReference 不但可以把对象 cache 起来,也不会造成内存不足的错误 (OutOfMemoryError)。我觉得 Soft Reference 也适合拿来实作 pooling 的技巧。
A obj = new A(); SoftRefenrence sr = new SoftReference(obj); //引用时 if(sr!=null){ obj = sr.get(); }else{ obj = new A(); sr = new SoftReference(obj); }
弱引用
当gc碰到弱可及对象,并释放abcWeakRef的引用,收集该对象。但是gc可能需要对此运用才能找到该弱可及对象。通过如下代码可以了明了的看出它的作用:
String abc=new String("abc"); WeakReference<String> abcWeakRef = new WeakReference<String>(abc); abc=null; System.out.println("before gc: "+abcWeakRef.get()); System.gc(); System.out.println("after gc: "+abcWeakRef.get());
运行结果:
before gc: abc
after gc: null
gc收集弱可及对象的执行过程和软可及一样,只是gc不会根据内存情况来决定是不是收集该对象。
如果你希望能随时取得某对象的信息,但又不想影响此对象的垃圾收集,那么你应该用 Weak Reference 来记住此对象,而不是用一般的 reference。
A obj = new A(); WeakReference wr = new WeakReference(obj); obj = null; //等待一段时间,obj对象就会被垃圾回收 ... if (wr.get()==null) { System.out.println("obj 已经被清除了 "); } else { System.out.println("obj 尚未被清除,其信息是 "+obj.toString()); } ... }
在此例中,透过 get() 可以取得此 Reference 的所指到的对象,如果返回值为 null 的话,代表此对象已经被清除。
这类的技巧,在设计 Optimizer 或 Debugger 这类的程序时常会用到,因为这类程序需要取得某对象的信息,但是不可以 影响此对象的垃圾收集。
PhantomRefrence(虚引用)
虚顾名思义就是没有的意思,建立虚引用之后通过get方法返回结果始终为null,通过源代码你会发现,虚引用通向会把引用的对象写进referent,只是get方法返回结果为null.先看一下和gc交互的过程在说一下他的作用.
1 不把referent设置为null, 直接把heap中的new String("abc")对象设置为可结束的(finalizable).
2 与软引用和弱引用不同, 先把PhantomRefrence对象添加到它的ReferenceQueue中.然后在释放虚可及的对象.
你会发现在收集heap中的new String("abc")对象之前,你就可以做一些其他的事情.通过以下代码可以了解他的作用.
import java.lang.ref.PhantomReference; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.lang.reflect.Field; public class Test { public static boolean isRun = true; public static void main(String[] args) throws Exception { String abc = new String("abc"); System.out.println(abc.getClass() + "@" + abc.hashCode()); final ReferenceQueue referenceQueue = new ReferenceQueue<String>(); new Thread() { public void run() { while (isRun) { Object o = referenceQueue.poll(); if (o != null) { try { Field rereferent = Reference.class .getDeclaredField("referent"); rereferent.setAccessible(true); Object result = rereferent.get(o); System.out.println("gc will collect:" + result.getClass() + "@" + result.hashCode()); } catch (Exception e) { e.printStackTrace(); } } } } }.start(); PhantomReference<String> abcWeakRef = new PhantomReference<String>(abc, referenceQueue); abc = null; Thread.currentThread().sleep(3000); System.gc(); Thread.currentThread().sleep(3000); isRun = false; } }
结果为
class java.lang.String@96354
gc will collect:class java.lang.String@96354