为什么要使用泛型?
未使用泛型的情况:
// 创建列表类
List list = new ArrayList();
// 添加一个类型为 String 的列表元素
list.add("hello");
// 强制转换为 String 类型,再赋值给类型为 s 的引用变量
String s = (String) list.get(0);
使用泛型的情况:
// 创建泛型类,<String> 为类型参数
List<String> list = new ArrayList<String>();
// 添加一个类型为 String 的列表元素
list.add("hello");
// 这里不需要强制类型转换
String s = list.get(0);
好处:实现通用的泛型算法,处理不同类型的集合,可以自定义类型,类型安全,便于阅读。
泛型类型
一个泛型类型是一个类型参数化(<类型参数>)的泛型类或接口。
一个简单的 Box 类
public class Box {
private Object object;
public void set(Object object) { this.object = object; }
public Object get() { return object; }
}
Box 类中方法接受或返回一个对象,除了基本类型外你可以传入任何对象。编译时无法检查类是如何使用的,如果传入一个 Integer 并希望获取 Integer,但却错误传入 String 对象,那么就会导致运行错误。
泛型版本 Box 类
泛型类型定义格式:
class name<T1, T2, ..., Tn> { /* ... */ } //T1, T2, ..., Tn为类型参数
类型参数(也成为类型变量)跟在类名称后面,类型参数放在尖括号(<>)中(T1, T2, ..., and Tn),
修改后的泛型 Box 类:
/**
* Box 类的泛型版本
* @param <T> 类型的值被装箱
*/
public class Box<T> {
// T 表示 "类型"
private T t;
public void set(T t) { this.t = t; }
public T get() { return t; }
}
上面代码中,Box 类中的所有 Object 类型都被替换为 T 类型,类型变量可以是指定的任何非基本类型:任何类类型、任何接口类型、任何数组类型,甚至是任何其他的类型变量。
泛型技术也可以实现通用的泛型接口。
类型参数的命名约定
按照惯例,类型参数名为单个大写字母。这与已知的变量命名约定形成鲜明对比,这样做有充分的理由:如果没有这个约定,那么很难区分类型变量和普通类或接口名称之间的区别。
最常用的类型参数名称:
- E - Element (used extensively by the Java Collections Framework)
- K - Key
- N - Number
- T - Type
- V - Value
- S,U,V etc. - 2nd, 3rd, 4th types
调用和实例化泛型类型
用具体的类型替换类型变量 T 就可以实例化泛型类型,例如:
Box <Integer> integerBox = new Box <Integer>();
通用类型的调用一般称为“参数化类型”。
像往常一样使用new
关键字实例化,但在类名和括号之间放置<Integer>
:
在 Java SE 7 和更高的版本中,只要编译器可以根据上下文确定或推断类型参数,就可以用一组空类型参数(<>)替换调用泛型类的构造函数所需的类型参数。例如:
Box<Integer> integerBox = new Box<>();
多类型参数
泛型类可以拥有多个类型参数。例如,实现通用 Pair 接口的通用 OrderedPair 类:
public interface Pair<K, V> {
public K getKey();
public V getValue();
}
public class OrderedPair<K, V> implements Pair<K, V> {
private K key;
private V value;
public OrderedPair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() { return key; }
public V getValue() { return value; }
}
以下语句创建 OrderedPair 类的两个实例:
Pair <String,Integer> p1 = new OrderedPair <String,Integer>(“Even”,8);
Pair <String,String> p2 = new OrderedPair <String,String>(“hello”,“world”);
新的OrderedPair <String,Integer>
代码将 K 实例化为一个字符串,将 V 实例化为一个整数。因此,OrderedPair 的构造函数的参数类型分别是 String 和 Integer。由于自动装箱,将 String 和 int 传递给类是有效的。
正如前文所述,由于Java编译器可以从OrderedPair <String,Integer>
声明中推断 K 和 V 类型,因此可以使用以下缩写:
OrderedPair <String,Integer> p1 = new OrderedPair <>(“Even”,8);
OrderedPair <String,String> p2 = new OrderedPair <>(“hello”,“world”);
参数化类型
您也可以用参数化类型(即List OrderedPair <K,V>
示例:
OrderedPair <String,Box <Integer>> p = new OrderedPair <>(“primes”,new Box <Integer>(...));
原始类型
原始类型是没有任何类型参数的泛型类或接口的名称。
例如,给定一个 Box 泛型类:
public class Box<T> {
public void set(T t) { /* ... */ }
// ...
}
要创建参数化类型,为形式类型参数 T 提供实际类型参数:
Box<Integer> intBox = new Box<>();
如果省略实际类型参数,则会创建一个Box<T>
的原始类型:
Box rawBox = new Box();
因此,Box 是泛型Box<T>
的原始类型。但是,非泛型类或接口类型不是原始类型。
在 JDK5.0 之前很多 API 类(如 Collections 类)不是通用的,为了向后兼容,允许将参数化类型分配给其原始类型:
Box<String> stringBox = new Box<>();
Box rawBox = stringBox; // OK
但是,将原始类型分配给参数化类型,编译器会发出一个警告:
Box rawBox = new Box(); // rawBox 是 Box<T> 的原始类型
Box<Integer> intBox = rawBox; // warning: unchecked conversion
如果使用原始类型来调用相应泛型类型中定义的泛型方法,则还会收到警告:
Box<String> stringBox = new Box<>();
Box rawBox = stringBox;
rawBox.set(8); // warning: unchecked invocation to set(T)
该警告显示原始类型绕过了泛型类型检查,将不安全代码的捕获推迟到运行时。因此,你应该避免使用原始类型。