管中窥虎
在学习
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
的其他几个简易的新特色会比较有趣些。