让我们来写一段实际代码来了解,泛型设计代码如何写。
下面是设计一个游戏中的npc管理器。
[code]//npc
public abstract class Npc{
//...
}
//士兵,继承了npc
public class Soldier extends Npc{
public void goAttack(Location loc){
//...
}
}
//野兽,继承了npc
public class Beast extends Npc{
public void goAttack(Location loc){
//...
}
}
public class NpcManager {
private LinkedList npcs= new LinkedList();
public T spawnNpc(Class npcType) {
T npc=null;
try {
npc= npcType.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
npcs.add(npc);
return npc;
}
//示例,运行正常,生成Soldier对象
public Soldier spawnSoldier(){
Soldier soldier= this.spawnNpc(Soldier.class);
return soldier;
}
//示例,运行正常,生成Beast对象
public Beast spawnBeast(){
Beast beast= this.spawnNpc(Beast.class);
return beast;
}
//报错,因为spawnNpc方法只能接受父类是Npc的类。
public NpcManager spawnSomeshit(){
NpcManager shit= this.spawnNpc(NpcManager.class);
return shit;
}
}
我们可以看到有三个类,Npc、Soldier、NpcManager。NpcManager管理着所有npc,但在游戏里npc种类有很多呀,比如小兵、野怪,他们都是npc所以继承同一个Npc抽象类。以后还可能有更多游戏中的npc种类例如交易商人Trader等,我们想创建npc并且让这个npc受NpcManager管理,是一种工厂模式,怎么办呢?给NpcManager设置一个生成npc的生产方法。
在接触泛型之前,我们可能会把写这样一个生产方法:
[code]public Npc spawnNpc(NpcType type){
Npc npc=null;
if(NpcType.SOLDIER.equal(type)){
npc= new Soldier();
}else if(NpcType.MONSTER.equal(type)){
npc= new Monster();
}else if(NpcType.TRADER.equal(type)){
npc= new Trader();
}
//....
return npc;
}
public enum NpcType {
SOLDIER,
MONSTER,
TRADER,
//....
}
这样的话,是可行,但是它增加了一项工作,就是需要创建一个枚举类添加每个新写的Npc子类,这样是多余的,繁琐容易出错。每当以后创建一个Npc子类都得在NpcType枚举类里添加一个这样的类型,并且还需要在spawnNpc(type)方法中加入对这个类型的生成支持代码,实属繁琐。
用了泛型以后呢,我们不需要这个NpcType枚举类了,传递进去新出的Npc子类的类型即可。
不管以后游戏更新出多少种Npc,我们都无需为生成这种npc再多写代码,还是那个泛型方法spanwNpc(type)。这就是泛型的好处。
再深入一些
泛型中,针对不确定的类型的对象,可以用T、?来替补,并且可以指定父类,用extends,例如
[code]public T spawnNpc(Class npcType) ;
其中可以理解为对T这个代码变量的修饰符,修饰它为Npc类的子类。默认是Object的子类。
在方法声明一行中:
T是不确定类型的对象。
T就像是编写代码时的变量,而不是运行时变量。
可以在方法声明中多次声明T,出现的三个T都是同一个类型。起到类型关联的作用。
T泛型如果要指定父类,要在声明处(例如方法声明处)用extends并用<>套起来。
T的泛型主要用于不确定类型多处关联的地方,例如方法的参数类型和返回结果类型。
T泛型不仅可以用在方法声明上,还可以类的声明上,我们常见的List、Set就是使用了这样的泛型特性。
除了T之外,还有成对出现、键值的泛型变量可以用,例如Map的实现。个人暂时用不到。
Note: Class是泛型类,所以可以写为Class>
那么还有一种写法,是用?问号替换T,例如
[code]private List super Npc> npcs;
public void register(Npc npc){
npcs.add(npc);
}
?问号的泛型使用总结:
?是不确定的类型,并非对象。
不具关联性,只对输入限定类型为某类子类,而没有且不与输出类型关联;如果一行声明出现多个?,那么每个?都没有关联,都是独立的。
只能用于声明泛型变量的类型。不可独立出现,因为,它代表一个未知类型,而不是T这样的一个未知类型的对象。
如果要指定父类,要在?后面加上 super加上父类名。
实用性:能让我们在写代码时,限制一个泛型变量的赋值必须为某类子类,或干脆直接完全未知,不限制父类。
但在读取使用未知类型的时候,取出来类型是Object,如果要使用子类特性,还需要仔细检查类型并需要强制转换。