泛型
本章思维导图
泛型入门
Java集合有个缺点——把一个对象“丢进”集合里之后,集合就会“忘记”这个对象的数据类型,当再次取出该对象时,该对象的编译类型就变成了Object类型(其运行时类型没变)。
编译时不检查类型的异常
下面代码将会看到编译时不检查类型所导致的异常。
import java.util.ArrayList;
import java.util.List;
public class ListErr {
public static void main(String[] args) {
// 创建一个只想保存字符串的List集合
List strList = new ArrayList();
strList.add("十年寒窗无人问");
strList.add("纵使相逢应不识");
// "不小心"把一个Integer对象"丢进"了集合
strList.add(5);
strList.forEach(str -> System.out.println(((String) str).length()));
}
}
上面程序创建了一个List集合,而且只希望该List集合保存字符串对象——但程序不能进行任何限制,上面程序将引发ClassCastException异常。
使用泛型
从Java5以后,Java引入了参数化类型(parameterized type)的概念,允许程序在创建集合时指定集合元素的类型。Java参数化类型被称为泛型(Generic)。
创建这种特殊集合的方法是:在集合接口 、类后增加尖括号,尖括号里放一个数据类型,即表明这个集合接口、集合类只能保存特定类型的对象。从而使集合自动记住所有集合元素的数据类型,从而无须对集合元素进行强制类型转换。
Java9增强的”菱形“语法
在Java7以前,如果使用带泛型的接口、类定义变量,那么调用构造器创建对象时构造器的后面也必须带泛型,这显得有些多余了。例如如下两条语句。
List<String> strList = new ArrayList<String>();
Map<String, Integer> scores = new HashMap<String, Integer>();
上面两条语句中的构造器后面的尖括号部分完全是多余的,在Java7以前这是必需的,不能省略。从Java7开始,Java允许在构造器后不需带完整的泛型信息,只要给出一对尖括号(<>)即可,Java可以推断尖括号里应该是什么泛型信息。上面两条代码可以改写为如下形式。
List<String> strList = new ArrayList<>();
Map<String, Integer> scores = new HashMap<>();
Java9再次增强了”菱形“语法,它甚至允许在创建匿名内部类时使用菱形语法,Java可根据上下文来推断匿名内部类中泛型的类型。下面代码示范了在匿名内部类中使用菱形语法。
interface Foo<T> {
void test(T t);
}
public class AnnoymousDiamond {
public static void main(String[] args) {
// 指定Foo类中泛型为String
Foo<String> f = new Foo<>() {
// test()方法的参数类型为String
public void test(String t) {
System.out.println("test方法的t参数为: " + t);
}
};
// 使用泛型通配符,此时相当于通配符的上限为Object
Foo<?> fo = new Foo<>() {
// tes()方法的参数类型为Object
public void test(Object t) {
System.out.println("test方法的Object参数为: " + t);
}
};
// 使用泛型通配符,通配符的上限为Number
Foo<? extends Number> fn = new Foo<>() {
// 此时test()方法的参数类型为Number
public void test(Number t) {
System.out.println("test方法的Number参数为: " + t);
}
};
}
}
上面的代码定义了带泛型声明的接口。
深入泛型
所谓泛型,就是允许在定义类、接口、方法时使用类型的形参,这个类型形参(或叫泛型)将在声明变量、创建对象、调用方法时动态地指定(即传入实际的类型参数,也可称为类型实参)。Java5改写了集合框架中的全部接口,为这些接口、类增加了泛型支持,从而可以在声明集合变量、创建集合对象时传入类型实参。
定义泛型接口、类
下面是Java5改写后List接口、Iterator接口、Map的代码片段。
// 定义接口时制定了一个泛型形参,该形参名为E
public interface List<E> {
// 在该接口里,E可作为类型使用
// 下面方法可以使用E作为参数类型
void add(E x);
Iterator<E> iterator();
...
}
// 定义接口时指定了一个泛型形参,该形参名为E
public interface Iterator<E> {
// 在该接口里E完全可作为类型使用
E next();
boolean hasNext();
...
}
// 定义接口时指定了一个泛型形参,该形参名为E
public interface Map<K, V> {
// 在该接口里K、V完全可作为类型使用
Set<K, V> keySet();
V put(K key, V value);
...
}
允许在定义接口、类时声明泛型形参,泛型形参在整个接口、类体内可当成类型使用,几乎所有可使用普通类型的地方都可以使用这种泛型形参。
可以为任何类、接口增加泛型声明,并不是只有集合类才可以使用泛型声明。
从泛型类派生子类
当创建了带泛型声明的接口、父类之后,可以为该接口创建实现类,或从该父类派生子类,当使用这些接口、父类时不能再包含泛型形参。
方法中的形参代表变量、常量、表达式等数据。定义方法时可以声明数据形参,调用方法时必须为这些数据形参传入实际的数据;与此类似的是,定义类、接口、方法时可以