List list = new ArrayList();
list.add("abc");
list.add(3);
for(int i = 0; i < list.size(); i++) {
String str = (String) list.get(i); //(1)
}
容器不加泛型,从容器中取元素的时候并进行类型转换时,(1)可能报类型强制转换异常ClassCastException。
创建一个List类包裹ArrayList添加add方法,在方法形参添加可以添加的类型。弊端是要手动创建多个List子类。
List<String> list = new ArrayList<>();
list.add("abc");
list.add(3); //(1)
尖括号中间类型标明该list只能装指定类型为String,装其他类型将报错,(1)编译器将报错。
List<String> list = new ArrayList<String>(); //(1)
List<String> list = new ArrayList<>(); //(2)
(1)为Java7之前的写法,(2)为Java7之后的写法。
public interface List<E> {
void add(E e);
...
}
public class Apple<T> {
private T info;
public Apple(T info) {
this.info = info;
}
public void setInfo(T info) {
this.info = info;
}
public T getInfo() {
return this.info;
}
public static void main(String[] args) {
Apple<String> apple = new Apple<>("abc");
}
}
如上例子分别声明带泛型的接口和类,使用时将泛型T改为实际类型即可。
从泛型声明的类或接口派生子类或子接口时,要指明父类泛型的具体类型而不能使用泛型,如下:
public class A extends Apple<T> {
}
编译器将报错。定义类、接口、方法时可以声明类型形参,使用类、接口、方法时应为类型形参传入实际的类型(类或接口可以不传入实际类型的参数)。
从Apple类派生子类,则改为如下代码:
//使用Apple类时为T形参传入String类型
public class A extends Apple<String> {
}
调用方法时必须为所有的数据形参传入参数值,与调用方法不同的是,使用类、接口时可以不为类型形参传入实际的类型参数,即下面代码也是正确的。
public class A extends Apple
从Apple派生子类,重写父类方法时,要注意泛型的实际类型,方法的返回类型和形参要和实际类型一致。
Collection cs = nwe ArrayList<String>();
if(cs instanceof List<String>) {...} // 错误
if(cs instanceof List) {...} // 可以使用
public void test(List c) {
...
}
类型通配符为?,元素类型可以匹配任何类型,如下:
public void test(List<?> c) {
...
}
c中元素永远为Object类型,因c是一个List,List运行时只存Object,泛型只是在编译期进行代码提示作用,实际在运行时存储类型为Object。
null为所有引用类型的实例。
Java提供了被限制的泛型通配符:
public void drawAll(List<?> shapes) {
for(Object obj : shapes) {
//强制类型转换
Shape s = (Shape) obj;
s.draw();
...
}
}
//标明通配符?表示List的泛型为Shape的所有子类型(包括本身),而不能是其他类型
public void drawAll(List<? extends Shape> shapes) {
//使用类型通配符上限,至少可以知道类型肯定为Shape,不需强转
for(Shape s : shapes) {
s.draw();
...
}
shapes.add(0, new Rectangle()); // (1)代码报错
}
(1)代码将报错,因虽然通配符的上限为Shape但?表示的实际类型并不知道,所以不能将对象放入其中。
static void fromArrayToCollection(Object[] a, Collection<Object> c) {
for(Object obj:a) {
c.add(obj);
}
}
以上方法的局限性为:只能将Object数组的元素复制到Object的Collection集合中,不能使用Collection
为了解决这个问题,引入泛型方法,语法格式如下:
修饰符 <T, S> 返回类型 方法名(形参列表) {
//方法体...
}
//如:
static <T> void fromArrayToCollection(T[] a, Collection<T> c) {
for(T o : a) {
c.add(o);
}
}
public static void main(String[] args) {
Integer[] ia = new Integer[100];
Collection<Number> cn = new ArrayList<>();
fromArrayToCollection(ia, cn);
}
泛型方法调用时,编译器根据实参推断类型形参的值,它通常推断出最直接的类型参数。Integer[] ia 使得T[] a知道T的类型为Integer。
class Foo<E> {
public <T> Foo(T t) {
...
}
public static void main(String[] args) {
new Foo("abc");
new Foo(3);
new <String> Foo("abc");
new <String> Foo(3.2); // (1) 报错
Foo<String> foo = new Foo<>(3); // 菱形语法标明E的类型,“abc”标明T的类型
Foo<String> foo = new <Integer> Foo<String>(3); // 指定泛型构造器中T的参数为Integer,则不能使用菱形语法
Foo<String> foo = new <Integer> Foo<>(3); // (2)报错,此处不能使用菱形语法
}
}
(1)处泛型构造器中T的参数为String类型,而实际传入的是Double类型
(2)显式指定泛型构造器中的泛型类型为Integer则不能使用菱形语法省略构造器后面<>中类的E的类型。
通配符的下限语法: super T>,只关心子类的具体类型而不关心父类的具体类型时,使用通配符的下限。
允许根据方法参数泛型不同进行方法重载,但是调用时,如果编译器分不清该调用哪个方法则编译报错,(3)报错。
public class MyUtils {
// (1)
public static <T> void copy(Collection<T> dest, Collection<? extends T> src) {...}
// (2)
public static <T> T copy(Collection<? super T> dest, Collection<T> src) {...}
public static void main(String[] args) {
List<Number> ln = new ArrayList<>();
List<Integer> li = new ArrayList<>();
copy(ln, li); // (3) 编译报错
}
}