Java泛型程序设计
@(Java)
1.使用泛型程序设计的原因
先说结论:使用泛型最显著的一个原因就是代码的重用。而且不需要对取值进行强制类型转换。
1.1未使用泛型的例子
public static void main(String[] args) {
List list = new ArrayList();
list.add("name");
list.add(2);
for (int i = 0; i < list.size(); i++) {
String res = (String) list.get(i);
System.out.println(res);
}
}
上述代码将会抛出一个ClassCastException。由于List没有指明元素类型,所有可以添加任何类对象。导致强制转换时出错。
1.2泛型的解决方案:类型参数
类型参数(type parameters)
public class GenericsTest {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("name");
// list.add(2); //编译报错
for (int i = 0; i < list.size(); i++) {
String res = (String) list.get(i);
System.out.println(res);
}
}
}
由上可知List<String>list = new ArrayList<String>();
能够明确的指出List列表中包含的String对象。
类型参数使得程序具有更好的可读性和安全性。
2.泛型类
泛型类(generic class)就是具有一个或多个类型变量的类。
public class MyValue<T> {
private T myValue;
public MyValue(T myValue){
this.myValue = myValue;
}
public T getMyValue() {
return myValue;
}
}
MyValue引入了一个类型变量T。泛型类可以有多个类型变量,例如还可以有MyValue<T,U>
等。
类型变量使用大写形式,且比较短。使用变量E表示集合的元素类型,K和V分别表示表的关键字与值的类型。T(需要时还可以用临近的字面U和S)表示“任意类型”。
public class GenericsTest {
public static void main(String[] args) {
//引用实例
MyValue<String> strValue = new MyValue<String>("name");
System.out.println(strValue.getMyValue());
MyValue<Integer> intValue = new MyValue<Integer>(123);
System.out.println(intValue.getMyValue());
}
}
3.泛型方法
泛型方法可在普通类中定义,也可在泛型类中定义。类型变量放在修饰符的后面,返回类型的前面。
public class GenericsTest {
public static void main(String[] args) {
String a = getValue("111");
System.out.println(a);
int b = getValue(11121);
System.out.println(b);
}
public static <T> T getValue(T value){
return value;
}
}
4.类型变量的限定
类或方法需要对类型变量加以约束。
例如:
public class GenericsTest {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("11");
list.add("22");
getValue(list);
// getValue("11"); //编译报错
}
public static <T extends Iterable<?>> void getValue(T numberOne){
Iterator<?> it = numberOne.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
}
}
由上可以知道,getValue方法限定了Iterable接口。只有实现了Iterable接口的参数才能使用。
为什么使用关键字extends而不是implements?
答:表示T应该是绑定类型的子类型(subtype)。T和绑定类型可以是类,也可以是接口。选择关键字extends的原因是更接近子类的概念,并且Java的设计者也不打算在语言中再添加一个新的关键字(如sub)
5.约束与局限性
5.1不能用基本类型实例化类型参数
比如没有MyValue<int>
,只有MyValue<Integer>
5.2运行时类型查询只适用于原始类型
虚拟机中的对象总有一个特定的非泛型类型。因此,所有的类型查询只产生原始类型。
MyValue<String> str = new MyValue<String>("str");
MyValue<Integer> inte = new MyValue<Integer>(123);
//结果为true
System.out.println(str.getClass() == inte.getClass());
上述代码两次调用getClass都返回的是MyValue.class
5.3不能创建参数化类型的数组
//编译报错
MyValue<String>[] strs = new MyValue<String>[10];
但是通过下述代码,能够通过数组存储检查,不过结果是不安全的。
public static void main(String[] args) {
Object[] objects = new Object[2];
objects[0] = new MyValue<String>("11");
objects[1] = new MyValue<Integer>(123);
for (int i = 0; i < objects.length; i++) {
System.out.println(((MyValue<?>)objects[i]).getMyValue());
}
}
如果需要收集参数化类型对象,只有一种安全而有效的方法:ArrayList<MyValue<String>>()
5.4Varargs警告
向参数个数可变的方法传递一个泛型类型。
调用这个方法时,Java虚拟机必须建立一个MyValue<String>
数组,这就违反了前面的规则。但是这种情况下,只会得到警告。可以使用@SuppressWarnings("unchecked")
或者Java 7的@SafeVarargs
来消除警告。
public static void main(String[] args) {
Collection<MyValue<String>> list = new ArrayList<MyValue<String>>();
MyValue<String> s1 = new MyValue<String>("1");
MyValue<String> s2 = new MyValue<String>("2");
add(list, s1,s2);
}
@SafeVarargs
public static <T> void add(Collection<T> list,T...value){
for (T t : value) {
list.add(t);
}
}
5.5不能实例化类型变量
不能使用像new T(…) ,new T[…]或T.class这样的表达式中的类型变量。
5.6泛型类的静态上下文中类型变量无效
在泛型类中不能再静态域或者方法中引用类型变量。
public class MyValue<T> {
//编译报错
private static T myValue;
public MyValue(T myValue){
this.myValue = myValue;
}
//编译报错
public static T getMyValue() {
return myValue;
}
}
5.7不能抛出或捕获泛型类的实例
既不能抛出也不能捕获泛型类对象。甚至泛型类扩展Throwable都是不合法的。
//编译报错
public class MyValue<T> extends Exception{
}
catch子句中不能使用类型变量。
try {
} catch (T e) {//编译报错
// TODO: handle exception
}
在异常规范中使用类型变量是允许的。
public static <T extends Exception> void doWork(T t)throws T{
}
5.8注意擦除后的冲突
例如在MyValue引入equals方法。
从概念上讲,它有两个equals方法:
boolean equals(String)
boolean equals(Object)
boolean equals(Object) 与 Object.equals 产生冲突,解决冲突的方法为从新定义新名称。
泛型规范解释还供给另一个原则: 要想支撑擦除的转换, 就须要强行限制一个类或类型变量不克不及同时成为两个接口类型的子类,而这两个接口是统一个接口的差别参数化。
class Calendar implements Comparable<Calandar>{ ... }
class GregorianCalendar extends Calendar implements Comparable<GregorianCalendar>
{} // ERROR
6.泛型类型的继承规则
Father是Son的父类,Son继承Father。
public class GenerticTest {
public static void main(String[] args) {
MyValue<Son> son = new MyValue<Son>(new Son("son"));
//编译出错
//MyValue<Father> father = son;
}
}
上述代码不合法。无论S和T是什么关系,MyValue<T>
与MyValue<S>
没有关系。
泛型类可以扩展或实现其他的泛型类。如ArrayList<T>
类实现List<T>
。
7.通配符类型
使用通配符来解决上述问题,MyValue<? extends Father>
的方法有:
? extends Father getMyValue();
void setMyValue(? extends Father)
但是不能调用setMyValue方法:
public static void main(String[] args) {
MyValue<Son> son = new MyValue<Son>(new Son("son"));
MyValue<Father> father = new MyValue<Father>(new Father("father"));
getValue(son);
getValue(father);
}
public static void getValue(MyValue<? extends Father> value){
System.out.println(value.getMyValue().getName());
//编译报错
//value.setMyValue(new Father("newFather"));
//value.setMyValue(new Son("newSon"));
}
编译器只知道需要某个Father的子类型,但不知道具体是什么类型。它拒绝传递特定的类型。
但是getMyValue是没有问题的,将getMyValue的返回值赋给Father的引用完全合法。
7.1通配符的超类型限定
例如:? super Son,限制为Son的所有类型。可以为方法提供参数,但不能使用返回值。MyValue<? super Son>
的方法有:
? super Son getMyValue();
void setMyValue(? super Son)
public static void main(String[] args) {
MyValue<Father> father = new MyValue<Father>(new Father("father"));
setValue(father);
//结果为newSon
System.out.println(father.getMyValue().getName());
}
public static void setValue(MyValue<? super Son> value){
value.setMyValue(new Son("newSon"));
//编译报错
//value.getMyValue().getName();
}
编译器不知道setMyValue的确切类型,但是可以用任意Son对象调用。而不能使用Father调用。然而调用getName,返回的对象类型不会得到保证,所以编译出错。
7.2无限定通配符
MyValue<?>
方法如下所示:
? getMyValue();
void setMyValue(?)
getMyValue的返回值只能赋给Object。setMyValue不能被调用,甚至不能用Object调用。MyValue与MyValue<?>
的不同在于:可以用任意Object对象调用原始MyValue类的setObject方法。
8.参考
《Java核心技术 卷1 基础知识(原书第9版)》