ArrayList<Integer> iList = newArrayList<>(); ArrayList<String> sList = newArrayList<>(); ArrayList list; // Raw use of parameterized class 'ArrayList' list = iList; list = sList;
上述代码,写一个从泛型版本的从 List 到数组的转换方法,由于不能从 List 中取得参数化类型 T,所以不得不从另一个额外参数中再传一个数组的组件类型进去,实属无奈。
通过擦除实现泛型,还丧失了一些面向对象应有的优雅,带来了一些模糊情况。
1 2 3 4 5 6 7 8 9
// 'method(List<String>)' clashes with 'method(List<Integer>)' // both methods have same erasure publicstaticvoidmethod(List<String> list) { System.out.println("invoke method(List<String> list)"); }
实际上这当然不是根据返回值来确定的,能编译和执行成功,是因为两个 method() 方法加入了不同的返回值后才能共存在一个 Class 文件中。方法重载要求方法具备不同的特征签名,返回值并不包含在方法的特征签名中,所以返回值不参与重载选择,但是在 Class 文件格式之中,只要描述符不是完全一致的两个方法,就可以共存。
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 转换,这种转换可能导致信息丢失。