publicclassDerivedResourceHog : MyResourceHog { // Have its own disposed flag. privatebool disposed = false;
protectedoverridevoidDispose(bool isDisposing) { // Don't dispose more than once. if (disposed) return; if (isDisposing) { // TODO: free managed resources here. } // TODO: free unmanaged resources here. // Let the base class free its resource. // Base class is responsible for calling // GC.SuppressFinalize() base.Dispose(isDisposing); // Set derived class disposed flag: disposed = true; } }
// DON'T DO THIS! publicclassBadClass { // Store a reference to a global object: privatestaticreadonly List<BadClass> FinalizedList = new List<BadClass>(); privatestring msg;
publicBadClass(string msg) { this.msg = msg; }
~BadClass() { // Add this object to the list. // This object is reachable, no longer garbage. // It's back! FinalizedList.Add(this); } }
protectedoverridevoidOnPaint(PaintEventArgs e) { // Bad. Created the same font every paint event. using (Font MyFont = new Font("Arial", 10.0f)) { e.Graphics.DrawString(DataTime.Now.ToStirng(), MyFont, Brushes.Black, new PointF(0, 0)); } base.OnPaint(e); }
系统会频繁调用 OnPaint(),而每次调用时,都会创建新的 Font 对象,但是这并没有必要,因为实际上这些对象都是一样的,因此垃圾回收器总是得回收旧的 Font。GC 的执行时机与程序所分配的内存数量以及分配的频率有关,如果总是分配内存,那么 GC 的工作压力就比较大,这自然会降低程序效率。
反之,将 Font 对象从局部变量改为成员变量,那么就可以复用同一个 Font:
1 2 3 4 5 6 7
privatereadonly Font myFont = new Font("Arial", 10.0f);
静态类型检查意味着编译器会把类型不符的用法找出来,这也令应用程序在运行期能够少做一些类型检查。然而有的时候还必须在运行期检查对象的类型,比如,如果所使用的框架已经在方法签名里把参数写成了 Object,那么可能就得先将该参数转成其他类型(例如其他的类或接口),然后才能继续编写代码。有两种办法能实现转换,一是使用 as 运算符,二是通过强制类型转换 (cast) 来绕过编译器的类型检查。在这之前,可以先通过 is 判断该操作是否合理,然后再使用 as 运算符 或执行强制类型转换。
在这两种方法中,应该优先考虑第一种办法,这样做要比盲目地进行类型转换更加安全,且在运行的时候更有效率。as 及 is 运算符不会考虑由用户所定义的转换。只有当运行期的类型与要转换到的类型相符合时,该操作才能顺利地执行。这种类型转换操作很少会为了类型转换而构建新的对象(但若用 as 运算符把装箱的值类型转换成未装箱且可以为 null 的值类型,则会创建新的对象)。
下面来看一个例子。如果需要把 object 对象转换为 MyType 实例,那么可以这样写:
1 2 3 4 5 6 7 8 9 10 11 12
object o = Factory.GetObject(); // Version one: MyType t = 0as MyType;
if(t!= null) { // work with t, it's a MyType } else { // report the failure }
此外,也可以这样写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
object o = Factory.GetObject(); // Version one: try { MyType t; t = (MyType) o; if (t != null) { // work with t, it's a MyType } } catch(InvalidCastException) { // report the conversion failure }
大家应该会觉得第一种写法比第二种更简单,而且更好理解。由于第一种写法不需要使用 try/catch 结构,因此程序的开销与代码量都比较低。如果采取第二种写法,那么不仅要捕获异常,而且还得判断t是不是 null。强制类型转换在遇到 null 的时候并不抛出异常,这导致开发者必须处理两种情况:一种是 o 本来就为 null,因此强制转换后所得的 t 也是 null;另一种是程序因 o 无法类型转换为 MyType 而抛出异常。如果采用第一种写法,那么由于 as 操作在这两种特殊情况下的结果都是 null,因此只需要用 if (t!= null) 来概括处理就可以了。
as 运算符与强制类型转换之间的最大区别在于如何对待由用户所定义的转换逻辑。as 与 is 运算符只会判断待转换的那个对象在运行期是何种类型,并据此做出相应的处理,除了必要的装箱与取消装箱操作,它们不会执行其他操作。如果待转换的对象既不属于目标类型,也不属于由目标类型所派生出来的类型,那么 as 操作就会失败。反之,强制类型转换操作则有可能使用某些类型转换逻辑来实现类型转换,这不仅包含由用户所定义的类型转换逻辑,而且还包括内置的数值类型之间的转换。例如可能发生从 long 至 short 转换,这种转换可能导致信息丢失。
// Broken - Inappropriate use of inheritance! publicclassInstrumentedHashSet<E> extendsHashSet<E> { // The number of attempted element insertions privateintaddCount=0;
public String toString() { return s.toString(); } }
Set 接口的存在使得 InstrumentedSet 类的设计成为可能,因为 Set 接口保存了 HashSet 类的功能特性。前文的基于继承的方法只适用于单个具体类,并且对于超类中所支持的每个构造器都要求有一个单独的构造器,与此不同的是,这里的包装类 (wrapper class) 可以被用来包装任何 Set 实现,并且可以结合任何先前存在的构造器一起工作:
1 2
Set<Instant> times = newInstrumentedSet<>(newTreeSet<>(cmp)); Set<E> s = newInstrumentedSet<>(newHashSet<>(INIT_CAPACITY));
只有当子类真正是超类的子类型 (subtype) 时,才适合用继承。对于 A、B 两类,只有两个类有 “is-a” 的关系时,B 才应该扩展 A。若想用 B 扩展 A,就应该问问自己:每个 B 是否 is an A?如果不能肯定,那么就不应该进行扩展。如果没有 is-a 的关系,通常情况下,B 包含 A 的一个私有实例,并暴露一个较小的、较简单的 API:A 本质上不是 B 的一部分,只是它的实现细节。
public Object pop() { if (size == 0) thrownewEmptyStackException(); return elements[--size]; }
/** * Ensure space for at least one more element, roughly doubling the capacity each time the array needs to grow. */ privatevoidensureCapacity() { if (elements.length == size) elements = Arrays.copyOf(elements, 2 * size + 1); } }
publicclassRule6 { publicstaticvoidmain(String[] args) { Stackstack=newStack(); for (inti=0; i < 10; i++) stack.push("1"); for (inti=0; i < 10; i++) stack.pop(); // break point System.out.println("complete"); // break point } }
// Can you spot the "memory leak"? classStack { privatestaticfinalintDEFAULT_INITIAL_CAPACITY=16; private Object[] elements; privateintsize=0;
publicStack() { elements = newObject[DEFAULT_INITIAL_CAPACITY]; }
public Object pop() { if (size == 0) thrownewEmptyStackException(); return elements[--size]; }
/** * Ensure space for at least one more element, roughly doubling the capacity each time the array needs to grow. */ privatevoidensureCapacity() { if (elements.length == size) elements = Arrays.copyOf(elements, 2 * size + 1); } }
public Object pop() { if (size == 0) thrownewEmptyStackException(); Objectresult= elements[--size]; elements[size] = null; return result; }
/** * Ensure space for at least one more element, roughly doubling the capacity each time the array needs to grow. */ privatevoidensureCapacity() { if (elements.length == size) elements = Arrays.copyOf(elements, 2 * size + 1); } }
// Hideously slow program! Can you spot the object creation? publicstaticvoidmain(String[] args) { Longsum=0L; for (longi=0; i < Integer.MAX_VALUE; i++) { sum += i; } System.out.println(sum); }
// Singleton with public final field publicclassElvis{ publicclassfinalElvisINSTANCE=newElvis(); privateElvis(){ ... } publicvoidleaveTheBuilding(){ ... } }
私有的构造器仅被调用一次,用来实例化共有的静态 final 域 Elvis.INSTANCE。由于缺少共有的或者受保护的构造器,所以保证了Elvis的全局唯一性:一旦 Elvis 类被实例化,只会存在一个Elvis实例,不多也不少。
// readResovle method to preserve singleton property private Object readResolve(){ // Return the one true Elvis and let the garbage collector take care of the Elvis impersonator return INSTANCE; }