文章目录
16.1 Java集合的泛型
在 JDK5 以前的版本中,集合中的元素都是 Object 类型,从集合中获取元素时,常常需要进行强制类型的转换。
以下代码向 ArrayList 中加入了一个 String 对象,接下来取出这个 String 对象,并试图把它强制转换为 Integer 类型。代码可以通过编译,但运行时会抛出 ClassCastException 运行时异常 。
List list = new ArrayList();
list.add("hello");
Integer i = (Integer)list.get(0); //抛出ClassCastException
在 JDK 版本升级的过程中,致力于把一些运行时异常转变为编译时错误。在 JDK5 版本中,引入了泛型的概念,它有助于把 ClassCastException 运行时异常转变为编译时的类型不兼容错误。
从 JDK5 开始,所有 Java 集合都采用了泛型机制。在声明集合变量和创建集合对象时,可以用 <>
标记指定集合中元素的类型:
List<String> list = new ArrayList<String>();
list.add("hello"); //合法
list.add(new Integer(11)); //编译出错,不允许Integer对象加入列表
Integer i = list.get(0); //编译出错,列表中元素 String 类型无法转换为 Integer类型
String s = list.get(0); //合法
Object o = list.get(0); //合法,允许向上转型,无需进行强制类型转换
Set<Object> set = new HashSet<Object>();
set.add("Tom"); //合法
set.add(new StringBuffer("Mike")); //合法
set.add(new Object()); //合法
set.add(new Integer(1)); //合法
16.2 定义泛型类和泛型接口
Java 集合的泛型机制到底是如何实现的呢?
public class OldBag {
private Object content;
public OldBag(Object content) {this.content = content;}
public Object get() {return this.content;}
public void set(Object content) {this.content = content;}
public static void main(String[] args) {
OldBag bag = new OldBag("Gucci");
Object content = (Integer)bag.get(); //抛出ClassCastException
}
}
//Bag类采用泛型机制改进OldBag类,避免抛出运行时异常
public class Bag<T> {
private T content;
public Bag(T content) {this.content = content;}
public T get() {return this.content;}
public void set(T content) {this.content = content;}
public static void main(String[] args) {
Bag<String> bag = new Bag<String>("Gucci");
Integer content1 = bag.get(); //编译出错
String content2 = bag.get(); //合法
}
}
就像在定义方法时可以声明一些方法参数 ,同样,在定义类时,也可以通过 <T>
的形式来声明类型参数。在类的主体中可以直接引用 T
这样的类型参数。这种带有类型参数的类被称作 泛型类。
一个泛型类可以有多个类型参数,语法为 ClassName<T1, T2, T3 ...>{}
public class MyMap<K, V> {
private Map<K, V> map = new HashMap<K, V>();
public void put(K k, V v) {
map.put(k, v);
}
public V get(K k) {
return map.get(k);
}
public int size() {
return map.size();
}
public static void main(String[] args) {
MyMap<Integer, String> map = new MyMap<Integer, String>();
map.put(1, "book one");
}
}
16.3 用extends关键字限定类型参数
在定义泛型类时,可以用 extends 关键字来限定类型参数,语法形式为:
<T extends ClassName> //T必须是指定类或其子类
<T extends InterfaceName> //T必须是指定接口的实现类
public class NumBag<T extends Number> {
private T content;
public NumBag(T content) {this.content = content;}
public T get() {return this.content;}
public void set(T content) {this.content = content;}
public static void main(String[] args) {
NumBag<List> bag1 = new LimitBag<List>(new ArrayList()); //编译出错
NumBag<Integer> bag2 = new LimitBag<Integer>(12); //合法
}
}
由于对 NumBag 类的类型参数 T 做了限定 <T extends Number>
,因此在 main() 方法中,NumBag<List>
是非法的,因为 List 不是 Number 类的子类,不允许把它赋值给 NumBag 类的类型参数 T。 而 NumBag<lnteger>
是合法的,因为 Integer 是 Number 类的子类,可以把它赋值给 NumBag 类的类型参数 T。
16.4 定义泛型数组
public class ArrayBag<T> {
private T[] content;
//private T[] content = new T[10]; 编译出错,不能用泛型来创建数组实例
public ArrayBag(T[] content) {this.content = content;}
public T[] get() {return this.content;}
public void set(T[] content) {this.content = content;}
public static void main(String[] args) {
String[] content = {"book1", "book2"};
ArrayBag<String> bag = new ArrayBag<String>(content);
}
}
以上 ArrayBag 类的 main() 方法先创建了一个 String 类型的数组,再把它传给 ArrayBag 类的构造方法,这是合法的。
16.5 定义泛型方法
在一个方法中,如果方法的参数或返同值的类型带有 <T>
形式的类型参数,那么这个方法称为泛型方法。 在普通的类或者泛型类中都可以定义泛型方法。
public class MethodTest {
public static <E> void printArray(E[] array) {
for (E element : array)
System.out.println(element);
}
public static <T extends Comparable<T>> T max(T x, T y) {
return x.compareTo(y) > 0 ? x : y;
}
public static void main(String[] args) {
Integer[] array = {1, 2, 3};
printArray(array);
System.out.println(max(1, 2));
}
}
MethodTest 类 的 printArray() 方法有一个类型参数 E
, printArray() 方法的 array 参数为 E[]
类型。MethodTest 类的 max() 方法有一个类型参数 T
,它被限定为是 Comparable 接口的实现类。 max() 方法的参数 x 和参数 y 均为 T
类型。
16.6 使用 ? 通配符
在泛型机制中,编译器认为 HashSet<String>
和 Set<String>
之间存在继承关系,因此以下赋值是合法的。但编译器认为 HashSet<Object>
和 HashSet<String>
之间不存在继承关系,因此以下赋值是非法的:
Set<String> set1 = new HashSet<String>(); //合法,允许向上转型
HashSet<Object> set2 = new HashSet<String>(); //编译出错,不兼容的类型
public class WildCastTest {
public static void printer1(Collection<Object> collection) {
for (Object obj : collection)
System.out.println(obj);
}
public static void printer2(Collection<?> collection) {
for (Object obj : collection)
System.out.println(obj);
}
public static void main(String[] args) throws Exception {
List<Integer> list = new ArrayList<Integer>();
list.add(11);
printer1(list); //编译出错,不兼容的类型
printer2(list); //合法
}
}
Collection<?>
表示集合中可以存放任意类型的元素,因此把 ArrayList<Integer>
类型的参数传给 printer2() 方法是合法的。
- 通配符
?
还可以与extends
关键字连用,用来限定类型参数的上限,例如:
以上类型1表示特定的类型,类型2只能是类型1或者是类型1的子类。例如:TreeSet<? extends type1> x = new TreeSet<Type2>();
TreeSet<? extends Number> x = new TreeSet<Integer>(); //合法 TreeSet<? extends Number> x = new TreeSet<String>(); //编译出错
- 通配符
?
还可以与super
关键字连用,用来限定类型参数的下限,例如 :
以上类型1表示特定的类型,类型2只能是类型1或者是类型1的父类。例如:TreeSet<? super type1> x = new TreeSet<Type2>();
TreeSet<? super Integer> x = new TreeSet<Number>(); //合法 TreeSet<? super Integer> x = new TreeSet<Byte>(); //编译出错
16.7 使用泛型的注意事项
在使用泛型时,还有以下注意事项:
- 在程序运行时,泛型类是被所有这种类的实例共享的。例如,尽管
ArrayList<String>
和ArrayList< lnteger>
类型在编译时被看作不同的类型,实际上在编译后的字节码类中,泛型会被擦除,ArrayList<Strin g>
和ArrayList<Integer>
类型均被看作是ArrayList
类型。因此所有泛型类的实例都共享同一个运行时类。//ArrayList<String> 和 ArrayList<Integer> 在运行时共享同一个ArrayList类的Class实例 List<String> l1 = new ArrayList<String>(); List<Integer> l2 = new ArrayList<Integer>(); System.out.println(l1.getClass() == l2.getClass()); //打印true
- 编译器不允许在一个类中定义两个同名的方法,分别以
List<String>
和List<Integer>
作为方法参数。例如以下是非法的方法重载 :public class OverloadTest{ public void test(List<String> ls) {} public int test(List<Integer> li) {} //非法重载 }
- 不能对确切的泛型类型使用 instanceof 操作。例如下面的操作是非法的,编译时会出错:
Collection cs = new ArrayList<String>(); if (cs instanceof Collection<String>) {...} //编译出错 if (cs instanceof Collection<?>) {...} //使用通配符通过编译
- 不能用泛型类型来进行强制类型转换,这样会存在安全隐患。例如下面的代码虽然编译能通过,但编译时会产生警告信息,并且运行时会抛出 ClassCastException:
Collection cs = new ArrayList<Integer>(); cs.add(1); ArrayList<String> list = (ArrayList<String>) cs; list.add("hello"); for (String str : list) System.out.println(str); //抛出异常ClassCastException