jdk1.5之八 —— Generics —— 3 定义泛型

虽然任何东西都应该是先造出来再用,但学习过程恰好相反:比如电脑,比如软件
——Fantasy Dog
 
知道了Generics的特性后,现在我们终于可以构造它了。
 
1.       基本的Generics类
public class Tree<V> {
V value;
V getValue(){return value;}
}
按照java的习惯(或者叫命名规范),类型参数(type variables)通常都是由一个大写字母表示,如V表示value,E(在java.util下很多)表示element——就像通常我们用i表示for循环里的迭代变量一样。
 
注意这里的V只能用于非静态的成员。Java的Generics不像c里的Templates。这里,Tree就是一个类,而不是像C一样,可以构造一个具有V类型的新类。因此,Tree类的静态成员是被所有Tree类对象共享的,而无论Tree<V>这个V是什么。从另一个方面讲,指定V为某个特定的类型(如Integer)时,就是指定了一个具有特定类型参数(Integer)的Tree对象,而Tree的静态成员是不可能从某个特定对象获取类型参数V的信息的——如果可以,再定义一个Tree<Double>怎么办?静态成员听谁的呢?
 
2.    Bound
Tree<V>表示Tree可以用任何类型作为其值。如果我们希望对V做点限制呢?比如我只希望Tree用Number为值,如何做到?
public class Tree<V Extends Number>{
    V value;
    V getValue(){return value}
}
先看V能干什么:
a.    作为参数来调用某个方法,或者作为方法的返回值
b.    用来声明一个类成员变量或局部变量
c.    作为其他Generics Type的类型参数(type argument)
d.    在cast时作为目标类型(如V val = (V)someThingIsNotV)
e.    作为某个parameterized type的方法的类型参数(泛型的方法后面会提到)
再看V不能做什么:
a.    创建对象
b.    创建数组
c.    用于异常处理(后面会提到)
d.    用于静态成员(前面已经说过了)
e.    用在instanceof表达式中
f.    作为父类
g.    In a class literal(T.class,它根本就不存在)
 
当然,这叫做Type variable Bound,即对V做的一个限制,这种限制与?没有什么不同。
这又回到了前一章那个我们悬而未决的问题:
什么时候用?,什么时候用V。
答案:很明显的,V能够做到的,?都做不到。但是这似乎并不是一个很好的区别?和V的准则。什么是呢?:
当你想说“任何一个Bound类的子类”,并且不管是什么类,都当作Bound来操作的话,用?
而当你想说“Boung类的某一个子类”,并且需要对该子类作某些操作(比如声明一个辅助变量 V temp),用V
 
3.    Wildcards(?)的另一个用法
这又是一个很复杂的问题。
考虑某个方法:
List<? extends Number> get(){return something;}
这是可能的,因为something可能是List<Number>, List<Integer>, 甚至是List<Double>,由该类的其他方法加入。
那么get方法返回的就是一个“未知类型参数的List,该未知类型是Number的子类”。把它返回给谁呢?
List<? extends Number> l = get();
由于类型参数不同的parameterized type之间没有相互的层次关系,因此l是任何确定类型参数的List都是非法的。它只能也是“未知类型参数的List,该未知类型是Number的子类”,当然,它也可能是List<?>,因为Object的子类包含了Number及其子类。
那这个l能做什么呢?l只能提取某个值,当然这个值得类型是未知的,你只能用其上限Number去操作它。相对的,add,setValue之类的对List操作的方法都不能调用——如前所述,如果你把extends改成super,就会得到相反的限制。
那我们为什么要返回一个这么“没用”的东西呢?因为也许在你的某个设计里就有可能需要它,谁也无法预测。因此,设计Generics是一件很难的事情——你必须慎重考虑Generics的种种不尽如人意的条例(规则)来实现你的各种需求。
 
4.    parameterized type Method
类型参数是不能用于静态成员或方法的,那我的静态方法需要类型参数,或者某个非静态方法需要类的类型参数之外的别的类型参数,咋整?
Public [static] <N extends Number> N get(N n){…}
就这么简单,在返回参数前加上一条<N extends Number>就可以了。
使用:
a.    当get有类型参数(N n)时,直接调用就可以(get(Integer), i.e.),因为编译器知道你的N是什么了。
b.    当类型参数N仅作为返回变量时,就需要:<Integer>get()这样调用了。
 
5.    数组,又是数组
也许你的Generics里面有这样的东西:E[] elements,那万一E被初始化成一个List<Number>怎么办?
放心,一来外面不可能传递一个List<Number>[]的数组进来(不允许声明),二来E不可以用来创建数组(这正是类型参数不可以创建数组的原因!!),这个element就绝对不可能成为一个非法的“parameterized type array”,因此在类里放心的定义E[]吧。
而对于varargs函数,method(T ...args),因为编译器对其的实现是通过隐式的创建一个T[] args数组,此T就不应该成为一个parameterized type……可是,可是结果居然是可以编译!
Java在parameterized type的数组方面做得是很差强人意的,我想这个问题我还不能有把握的考究出来,有心钻研的人大概可以等Thinking in Java 4 th来解释了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值