Vincent

Vicent's blog
随笔 - 74, 文章 - 0, 评论 - 5, 引用 - 0
数据加载中……

generic-泛型/类属(三)

管中窥虎

在学习 java 1.5 的过程中,我使用了 sun 公布的 tutorial ,这份文档写的比较详尽易明,但是对于想快速了解 tiger 而且具有较好 java 基础的人来说,大篇幅的英文文档是比较耗时间和非必需的,所以我将会归纳这份文档的主要内容,在保证理解的底线上,尽力减少阅读者需要的时间。

 

在以下地址可以进入各新增语言特色介绍以及下载相关文档(若有)。

http://java.sun.com/j2se/1.5.0/docs/relnotes/features.html

 

这一篇是接着上两篇继续的。

第一道虎纹: generic -泛型 / 类属(三)

一些零碎

 

关于类:

 

 

List  <  String  >  l1  =   new  ArrayList  <  String  >  (); 

List 
<  Integer  >  l2  =   new  ArrayList  <  Integer  >  (); 

System.out.println(l1.getClass() 
==  l2.getClass()); 

 

这样的代码打印出什么?脑子有没有点混乱?事实是 true ,虽然类型参数不一样,但它们在运行时终归是同一个类。一个 class 可以有不同的 type 。由于静态的变量和方法是被这个类的所有实例共享的,所以在静态的方法,初始化块,静态变量的声明或初始化中引用类型变量(前面的 T 这一类的东西)是非法的。

 

关于转换和 instanceOf

 

正因为类的类型是被所有的实例共享的,所以去问一个实例是否为特定的类型是无意义的。

Collection cs  =   new  ArrayList  <  String  >  (); 

if  (cs  instanceof  Collection  <  String  >  )   }   //  illegal 

这样的代码是非法的。

 

Collection  <  String  >  cstr  =  (Collection  <  String  >  ) cs;  //  unchecked warning 

 

同样的,上面这行代码将会有 unchecked warning 因为它试图做的类型检查,是根本不会在运行时被执行的。同样的,类型变量也是无效的:

 

<  T  >  T badCast(T t, Object o)  return  (T) o;  //  unchecked warning 

}
 

 

总而言之,类型变量在运行时是不存在的,意味着它们不会对运行表现增加任何时间上或者空间上的累赘,这样挺好,但同时也意味着,别指望用它们来做类型转换。

 

 

关于数组

 

一个数组对象的元素类型是不能为类型变量或者带类型参数的类型,除非是一个非受限通配符类型,你可以声明一个元素类型为类型变量或者带类型参数的类型的数组类型,但是不能声明这样的数组对象。好吧,你舌头打结了吧?说实话,在翻译这段话前,我不仅舌头打结,连神经都快打结了,即使如此,我还是不能肯定我翻译的是否正确。原文如下:

The component type of an array object may not be a type variable or a parameterized

type, unless it is an (unbounded) wildcard type.You can declare array types whose

element type is a type variable or a parameterized type, but not array objects.

通过阅读下面的代码,也许帮助你理解这团乱麻,之所以有上面这样的复杂规定,是为了避免这样的情形:

 

List  <  String  >  [] lsa  =   new  List  <  String  >  [ 10 ];  //  实际上不被允许,现在只是假设可以这样写 

Object o 
=  lsa; 

Object[] oa 
=  (Object[]) o; 

List 
<  Integer  >  li  =   new  ArrayList  <  Integer  >  (); 

li.add(
new  Integer( 3 )); 

oa[
1 =  li;  //  有错,但通过了运行时的检查 

String s 
=  lsa[ 1 ].get( 0 );  //  run-time error - ClassCastException 

 

如果运行了数组元素为带类型变量的类型,就会出现这样的情况,明明通过了编译,没有任何 unchecked warning ,但在运行时却出错。所以泛型必须设计为这个样子,以保证:如果你的整个应用程序在 jdk1.5 下通过了编译而且没有 unchecked warning ,那它就是类型安全的。

然而,你还可以用通配符数组,接下来是以上代码的两个改版。

 

第一个让数组类和数组对象都用了通配符,在最后一句,如果要赋值给 String ,就必须用显示的类型转换,虽然出了错,那么可以说这不是机制的错误了,而是程序员的错。

 

List  <   ?   >  [] lsa  =   new  List  <   ?   >  [ 10 ];  //  ok, array of unbounded wildcard type 

Object o 
=  lsa; 

Object[] oa 
=  (Object[]) o; 

List 
<  Integer  >  li  =   new  ArrayList  <  Integer  >  (); 

li.add(
new  Integer( 3 )); 

oa[
1 =  li;  //  correct 

String s 
=  (String) lsa[ 1 ].get( 0 );  //  run time error, but cast is explicit 

 

 

 

在第二个版本里,我们定义了带类型参数的类型的数组类,但数组对象用了通配符,这样是合法的,但不安全,产生了 unchecked warning ,而且最终也确实出错了。但至少,我们得到了警告。

 

List  <  String  >  [] lsa  =   new  List  <   ?   >  [ 10 ];  //  unchecked warning - this is unsafe! 

Object o 
=  lsa; 

Object[] oa 
=  (Object[]) o; 

List 
<  Integer  >  li  =   new  ArrayList  <  Integer  >  (); 

li.add(
new  Integer( 3 )); 

oa[
1 =  li;  //  correct 

String s 
=  lsa[ 1 ].get( 0 );  //  run time error, but we were warned 

 

 

 

同样的,试图创造一个元素类型是类型变量的数组对象是通过不了编译的:

 

这些错误的原因归结起来也是我们之前讨论过的了,就是因为运行时,这些类型变量是不存在的,无法决定数组的实际类型。

要突破这些限制,可以使用 类名称字面常量( class literal )作为运行时类型标记,请看下文。

 

 

以类名称字面常量作为运行时类型标记

Jdk1.5 里, java.lang.Class 是泛型的,这就有趣了, Class 类有个类型参数 T ,那 T 代表什么? T 代表了 Class 类的对象所代表的类型,又来绕口令了不是?

举例吧: String.class 的类型就是 Class<String> Serializable.class 的类型就是 Class < Serializable > 。现在 Class 类的 newInstance() 方法返回一个 T ,你现在在创造对象的时候获得的类型更加精确了。( 1.4 里是固定地返回 Object

假设你要写一个工具方法,从数据库里执行一个 SQL 查询,将符合查询的结果以对象集合返回,有一钟方法就是显示的使用一个工厂对象,如下:

interface  Factory  <  T  >   { T make(); }  

public   <  T  >  Collection  <  T  >  select(Factory  <  T  >  factory, String statement) 

       Collection 
<  T  >  result  =   new  ArrayList  <  T  >  (); 

/*  run sql query using jdbc  */  

     
for  ( /*  iterate over jdbc results  */  ) 

      T item 
=  factory.make(); 

/*  use reflection and set all of item’s fields from sql results  */  

      result.add(item); 

      }
 

      
return  result; 

}
 

你可以这样调用它:

 

select( new  Factory  <  EmpInfo  >  () { 

public  EmpInfo make() { 

   return   new  EmpInfo(); 



} , ”selection string”); 

 

或者声明一个实现 Factory 接口的类 EmpInfoFactory

class  EmpInfoFactory  implements  Factory  <  EmpInfo  >   { 

public  EmpInfo make() { 

  
return   new  EmpInfo(); 





然后这样调用它:

select(getMyEmpInfoFactory(), ”selection string”); 

 

这两个办法的缺点就是,你要么在调用处写上罗嗦的工厂类,要么为每个类都写一个工厂类,在每个调用的地方传入一个工厂对象,这看起来也不怎么自然。

类名称字面常量来作为一个工厂对象就很自然,它可以用于反射机制,如果不用泛型,可以这样写:

C...ollection emps = sqlUtility.select(EmpInfo.class, ”select * from emps”);

public   static  Collection select(Class c, String sqlStatement) { 

   Collection result 
=   new  ArrayList(); 

   
/*  run sql query using jdbc  */  

   
for  (  /*  iterate over jdbc results  */  ) { 

       Object item 
=  c.newInstance(); 

      
/*  use reflection and set all of item’s fields from sql results  */  

      result.add(item); 

   } 
return  result; 


 

然而这样我们得不到我们想要的包含确切的类型的对象集,而现在 Class 是泛型的了,我们可以用它来达到目的:

Collection  <  EmpInfo  >  emps  =   sqlUtility.select(EmpInfo. class , ”select  *  from emps”); 

public   static   <  T  >  Collection  <  T  >  select(Class  <  T  >  c, String sqlStatement) { Collection  <  T  >  result  =   new  ArrayList  <  T  > (); 

   
/*  run sql query using jdbc  */  

   
for  (  /*  iterate over jdbc results  */  ) { T item  =  c.newInstance(); 

      
/*  use reflection and set all of item’s fields from sql results  */  

      result.add(item); 

   } 
return  result; 


精确的类型,类型安全。齐活儿了。

 

这一篇到此为止,泛型的部分还剩下比较复杂的两个部分,经过考虑后决定延后完成,先完成 tiger 的其他几个简易的新特色会比较有趣些。

posted on 2006-08-22 11:17 Binary 阅读(332) 评论(0)  编辑  收藏 所属分类: j2se


只有注册用户登录后才能发表评论。


网站导航: