1.泛型是什么?程序员
泛型(Generic type 或者 generics)是对 Java 语言的类型系统的一种扩展,以支持建立能够按类型进行参数化的类。数组
能够在集合框架(Collection framework)中看到泛型的动机。例如,Map 类容许您向一个 Map 添加任意类的对象,即便最多见的状况是在给定映射(map)中保存某个特定类型(好比 String)的对象。安全
由于 Map.get() 被定义为返回 Object,因此通常必须将 Map.get() 的结果强制类型转换为本身指望的类型,如:框架
Map m = new HashMap();
m.put("key", "blarg");
String s = (String) m.get("key");性能
可是有可能某人已经在该映射中保存了不是 String 的东西,这样的话,上面的代码将会抛出 ClassCastException。优化
理想状况下,您可能会得出这样一个观点,即 m 是一个 Map,它将 String 键映射到 String 值。这可让您消除代码中的强制类型转换,同时得到一个附加的类型检查层,该检查层能够防止有人将错误类型的键或值保存在集合中。这就是泛型所作的工做。对象
2.泛型的好处接口
Java 语言中引入泛型是一个较大的功能加强。不只语言、类型系统和编译器有了较大的变化,以支持泛型,并且类库也进行了大翻修,因此许多重要的类,好比集合框架,都已经成为泛型化的了。这带来了不少好处:开发
类型安全。 泛型的主要目标是提升 Java 程序的类型安全。经过知道使用泛型定义的变量的类型限制,编译器能够在一个高得多的程度上验证类型假设。没有泛型,这些假设就只存在于程序员的头脑中(或者若是幸运的话,还存在于代码注释中)。get
Java 程序中的一种流行技术是定义这样的集合,即它的元素或键是公共类型的,好比“String 列表”或者“String 到 String 的映射”。经过在变量声明中捕获这一附加的类型信息,泛型容许编译器实施这些附加的类型约束。类型错误如今就能够在编译时被捕获了,而不是在运行时看成 ClassCastException 展现出来。将类型检查从运行时挪到编译时有助于您更容易找到错误,并可提升程序的可靠性。
消除强制类型转换。 泛型的一个附带好处是,消除源代码中的许多强制类型转换。这使得代码更加可读,而且减小了出错机会。
尽管减小强制类型转换能够下降使用泛型类的代码的罗嗦程度,可是声明泛型变量会带来相应的罗嗦。比较下面两个代码例子。
该代码不使用泛型:
List li = new ArrayList();
li.put(new Integer(3));
Integer i = (Integer) li.get(0);
该代码使用泛型:
List<Integer> li = new ArrayList<Integer>();
li.put(new Integer(3));
Integer i = li.get(0);
在简单的程序中使用一次泛型变量不会下降罗嗦程度。可是对于屡次使用泛型变量的大型程序来讲,则能够累积起来下降罗嗦程度。
潜在的性能收益。 泛型为较大的优化带来可能。在泛型的初始实现中,编译器将强制类型转换(没有泛型的话,程序员会指定这些强制类型转换)插入生成的字节码中。可是更多类型信息可用于编译器这一事实,为将来版本的 JVM 的优化带来可能。
因为泛型的实现方式,支持泛型(几乎)不须要 JVM 或类文件更改。全部工做都在编译器中完成,编译器生成相似于没有泛型时所写的代码,只是更能确保类型安全而已。
3.泛型用法的例子
下面的代码示例展现了 JDK 5.0 中集合框架中的 Map 接口的定义的一部分:
public interface Map<K, V> {
public void put(K key, V value);
public V get(K key);
}
注意该接口的两个附加物:
类型参数 K 和 V 在类级别的规格说明,表示在声明一个 Map 类型的变量时指定的类型的占位符。
在 get()、put() 和其余方法的方法签名中使用的 K 和 V。
为了赢得使用泛型的好处,必须在定义或实例化 Map 类型的变量时为 K 和 V 提供具体的值。以一种相对直观的方式作这件事:
Map<String, String> m = new HashMap<String, String>();
m.put("key", "blarg");
String s = m.get("key");
当使用 Map 的泛型化版本时,您再也不须要将 Map.get() 的结果强制类型转换为 String,由于编译器知道 get() 将返回一个 String。
在使用泛型的版本中并无减小键盘录入;实际上,比使用强制类型转换的版本须要作更多键入。使用泛型只是带来了附加的类型安全。由于编译器知道关于您将放进 Map 中的键和值的类型的更多信息,因此类型检查从执行时挪到了编译时,这会提升可靠性并加快开发速度。
4.命名类型参数
推荐的命名约定是使用大写的单个字母名称做为类型参数。这与 C++ 约定有所不一样(参阅 附录 A:与 C++ 模板的比较),并反映了大多数泛型类将具备少许类型参数的假定。对于常见的泛型模式,推荐的名称是:
K —— 键,好比映射的键。
V —— 值,好比 List 和 Set 的内容,或者 Map 中的值。
E —— 异常类。
T —— 泛型。
5.泛型不是协变的
关于泛型的混淆,一个常见的来源就是假设它们像数组同样是协变的。其实它们不是协变的。List<Object> 不是 List<String> 的父类型。
若是 A 扩展 B,那么 A 的数组也是 B 的数组,而且彻底能够在须要 B[] 的地方使用 A[]:
Integer[] intArray = new Integer[10];
Number[] numberArray = intArray;
上面的代码是有效的,由于一个 Integer 是 一个 Number,于是一个 Integer 数组是 一个 Number 数组。可是对于泛型来讲则否则。下面的代码是无效的:
List<Integer> intList = new ArrayList<Integer>();
List<Number> numberList = intList; // invalid
这样作不少人以为麻烦,但避免了以下的问题:
Person[] person = new Student[5];
person[0] = new Emploee();
6.类型通配符
假设您具备该方法:
void printList(List l) {
for (Object o : l)
System.out.println(o);
}
上面的代码在 JDK 5.0 上编译经过,可是若是试图用 List<Integer> 调用它,则会获得警告。出现警告是由于,您将泛型(List<Integer>)传递给一个只承诺将它看成 List(所谓的原始类型)的方法,这将破坏使用泛型的类型安全。
若是试图编写像下面这样的方法,那么将会怎么样?
void printList(List<Object> l) {
for (Object o : l)
System.out.println(o);
}
它仍然不会经过编译,由于一个 List<Integer> 不是 一个 List<Object>(正如前一屏 泛型不是协变的 中所学的)。这才真正烦人 —— 如今您的泛型版本尚未普通的非泛型版本有用!
解决方案是使用类型通配符:
void printList(List<?> l) {
for (Object o : l)
System.out.println(o);
}
上面代码中的问号是一个类型通配符。它读做“问号”。List<?> 是任何泛型 List 的父类型,因此您彻底能够将 List<Object>、List<Integer> 或 List<List<List<Flutzpah>>> 传递给 printList()。