在泛型中使用通配符和继承
本文是Sun官方以Blog形式发布的Java核心技术窍门(JavaCoreTechTip)中的一篇,它以非常简洁的示例展示了泛型通配符的使用,初学Java泛型的朋友可以看看。(2009.12.30最后更新)
Java2平台,标准版5.0(J2SE 5.0)为Java程序设计语言及其平台引入了泛型。在最简单的案例和典型的应用中,泛型能够识别集合容器中所存储的是否是你所期望的对象。所以,你可以特别说你有一个String或其它类型对象的List,而不是声称你的程序有一个Object的List。所以,如果你不小心向该List中加入了错误类型的对象,编译器会告之你这个错误。该错误将在编译时进行修复,而不用等到你运行该程序,且在程序运行到该处代码时,在获取对象的操作中产生一个运行时的强制类型转换异常。
这就提出了泛型的第二个好处。迭代器将变得类型安全了。Iterator接口中的next()方法将会返回集合中下一个元素的类型安全版本。
但这并不是本文要介绍的泛型应用的窍门,那些窍门已由2005 Core Java Technologies Tip描述过了。在使用泛型时,大多数人都不能很好地理解对extends关键字的使用。一个典型的描述如何使用extends关键字的示例与绘制图形有关。与其不同的是,此处窍门所用的示例将使用Swing组件,以便你不必创建额外的新类。在一个非常有限的例子中,Swing按钮组件的类层次结构如下所示,当然,Object是实际上的根。
Component
|- Container
|- JComponent
|- AbstractButton
|- JButton
|- JMenuItem
|- JCheckBoxMenuItem
|- JMenu
|- JRadioButtonMenuItem
|- JToggleButton
|- JCheckBox
|- JRadioButton
所有AbstractButton的子类都共同享有的一个东西就是方法getText。这就是泛型的精髓,你能定义一个方法去处理以AbstractButton为元素的List,并返回这些按钮的String类型的标签的List。下面是该方法的第一个版本:
public static List<String> getLabels(List<AbstractButton> list) {
List<String> labelList = new ArrayList<String>(list.size());
for (AbstractButton button: list) {
labelList.add(button.getText());
}
return labelList;
}
下面就是如何使用该方法。首先,定义一个AbstractButton类型的List,然后向其中填充值,并调用该方法:
List<AbstractButton> buttonList = new ArrayList<AbstractButton>();
buttonList.add(new JButton("Hello"));
buttonList.add(new JCheckBox("World"));
buttonList.add(new JRadioButton("Hola"));
buttonList.add(new JMenuItem("Mundo"));
List labels = getLabels(buttonList);
System.out.println(labels);
根据Google,"Hola, Mundo"是"Hello, World"的西班牙译文。调用println()方法的结果如下所示:
[Hello, World, Hola, Mundo]
对于AbstractButton的List对象,一切都能正常运行,但当是其它类型,特别是AbstractButton子类型的List时,就不能正常工作了。从逻辑上,有人可能认为对于以JButton为元素的List,一切仍能正常工作。因为JButton是AbstractButton的子类。难道不能对AbstractButton子类型的List调用方法getLabels(List<AbstractButton>)?
然而,事实并非如此。因为这是一个编译时检查,同时也因为getLabels方法被定义为只接受AbstractButton的List,你不能向该方法中传入任何其它类型的List。
GetList.java:13: getLabels(java.util.List<javax.swing.AbstractButton>)
in GetList cannot be applied to (java.util.List<javax.swing.JButton>)
List<String> labels = getLabels(buttonList);
^
1 error
这也就是extends关键字发挥作用的地方了。不将getLabes方法定义为仅仅接受AbstractButton List,而是将它定义为接受AbstractButton子类的List:
public static List<String> getLabels(
List<? extends AbstractButton> list)
此处的通配符?表明该方法并不关心确切的类型是什么,只要它是AbstractButton的子类型即可。下面是综合了前述所有代码片断的完整示例程序:
import java.util.*;
import javax.swing.*;
public class GetList {
public static void main(String args[]) {
List<JButton> buttonList =
new ArrayList<JButton>();
buttonList.add(new JButton("Hello"));
buttonList.add(new JButton("World"));
buttonList.add(new JButton("Hola"));
buttonList.add(new JButton("Mundo"));
List labels = getLabels(buttonList);
System.out.println(labels);
}
public static List<String> getLabels(
List<? extends AbstractButton> list) {
List<String> labelList = new ArrayList<String>(list.size());
for (AbstractButton button: list) {
labelList.add(button.getText());
}
return labelList;
}
}
现在,当你要用泛型来定义你自己的类和方法时,就要考虑接受作为泛型参数的抽象类,或它的任一超类,记得使用通配符以便相同的方法对于子类也能很好地工作。
更多关于泛型的信息,请见两篇较早前由Gilad Bracha撰写的教程:一篇是2004年的教程(PDF),另一篇是在线的Java Tutorial中的泛型章节。