Java:泛型
一、泛型的简单介绍
1. 泛型的引入
JDK 1.5中引入了泛型这个新特性,泛型的本质是参数化类型(Parameterized Types)的应用,也就是指操作的数据类型被指定为一个参数,之后使用到该数据时必须符合指定的类型。这种参数化类型可以在类、接口和方法中使用,分别称为泛型类、泛型接口和泛型方法。
2. 为什么引入泛型
泛型思想在C++模板(Templates)中开始萌芽,在Java还没有泛型的时候,只能通过Object类型是所有类型的父类型和类型强制转换两个特点的配合使用来实现类型泛化。例如在哈希表的存取中,JDK 1.5之前使用HashMap的 get( ) 方法,返回值就是一个Object对象。由于Java中所有的类型都继承自java.lang.Object,那Object可能转换为任何类型的对象。这就带来了一个缺陷:在编译期间,编译器无法检查这个Object的强制转换是否成功,只有程序员和编译期间的虚拟机才知道这个Object到底是什么类型的对象。于是导致运行期间的ClassCastException风险增加。
3. Java的“伪泛型”
在说明Java为什么是“伪泛型”之前,先简单的介绍一下C#的“真实泛型”:
C#泛型是C# 2.0版本中新增的特性,C#无论语言层面还是CLR都提供对泛型的支持,所以C#泛型类或方法编译成Microsoft中间语言(MSIL)时,它依旧包含将其表示为具有类型参数的元数据,也就是说泛型是真实存在的。类型没有变成原始类型,而是通过类型膨胀实现,在运行期生成自己的虚方法和类型数据。所以C# 是“真实泛型”。
而Java的中的泛型只存在于源程序中,在编译后的字节码中,泛型就被替换为原始类型(Raw Type),并且在相应的地方插入了强制转换代码。因此,对于运行期的Java来说,ArrayList<int>与ArrayList<String>就是同一个类。所以说泛型是Java的一颗语法糖,Java中泛型实现的方法称为类型擦除,基于这种方法实现的泛型称为“伪泛型”。
4. 泛型中基本术语
以 ArrayList<E> 和 ArrayList<Integer> 为例:
(1)“ArrayList<E>”称为泛型类型;
(2)ArrayList<E> 中的“E”称为类型变量或类型参数;
(3)“ArrayList<Integer>”称为参数化类型;
(4)ArrayList<Integer>中的“Integer”称为类型参数的实例,或实际类型参数;
(5)ArrayList<Integer>中的“<Integer>”读作“type of Integer”;
(6)“ArrayList”称为原始类型
5. 类型参数的命名惯例
通过这些类型参数的命名惯例,方便我们理解不同类型参数之间的区别与含义
(1)E - Element
(2)K - Key
(3)N - Number
(4)T - Type
(5)V - Value
(6)S,U,V - 第二个、第三个、第四个Type
二、 泛型的使用
1. 泛型类的定义和使用
(1)泛型类(generic class)是具有一个或多个类型变量的类。定义一个泛型类只需在类名后面加上“<>”,然后在尖括号里面加上类型参数。下面是非泛型类和泛型类代码实现的简单对比:
// 非泛型Box类 public class Box { private Object object; public void set(Object object) { this.object = object; } public Object get() { return object; } }
由于Box类接收和返回的是Object,所以你可以传任何类型的值。假如你传了一个Integer类型的值,但是你想返回一个String类型的值,就会发生运行时错误。
/** * 泛型Box类 * @param <T>是 Box类内部值的类型 */ public class Box<T> { // T stands for "Type" private T t; public void set(T t) { this.t = t; } public T get() { return t; } }
Box泛型类使用类型参数T代替了Object,一个类型参数可以是任何非原始类型(non-primitive type),比如,任何class type,inteface type,array type,或是一个类型参数
(2)调用并实例化泛型类
调用并实例化一个泛型类,只需使用任意具体类型代替泛型类型“T”即可,比如 Integer:
Box<Integer> integerBox = new Box<Integer>();
2. 泛型接口的定义和使用
泛型接口的定义和使用与泛型类相似
import java.util.Date; interface Show<T, U> { void show(T t, U u); } class ShowTest implements Show<String, Date> { public static void main(String[] args) { ShowTest test = new ShowTest(); test.show("Time is", new Date()); } public void show(String str, Date date) { System.out.println(str + " " + date); } }
运行结果:
3. 泛型方法的定义和使用
泛型类在多个方法签名间实施类型约束。在 List<V> 中,类型参数 V 出现在 get()、add()、contains() 等方法的签名中。当创建一个 Map<K, V> 类型的变量时,您就在方法之间宣称一个类型约束。您传递给 add() 的值将与 get() 返回的值的类型相同。
类似地,之所以声明泛型方法,一般是因为您想要在该方法的多个参数之间宣称一个类型约束。
public static void main(String[] args) throws ClassNotFoundException { String str=get("Hello", "World"); System.out.println(str); } public static <T, U> T get(T t, U u) { if (u != null) return t; else return null; }
三、 泛型变量的类型限定
在上面,我们简单的学习了泛型类、泛型接口和泛型方法。我们都是直接使用<T>这样的形式来完成泛型类型的声明。
有的时候,类、接口或方法需要对类型变量加以约束。看下面的例子:
有这样一个简单的泛型方法:
public static <T> T get(T t1,T t2) { if(t1.compareTo(t2)>=0);//编译错误 return t1; }
因为,在编译之前,也就是我们还在定义这个泛型方法的时候,我们并不知道这个泛型类型T,到底是什么类型,所以,只能默认T为原始类型Object。所以它只能调用来自于Object的那几个方法,而不能调用compareTo方法。
可我的本意就是要比较t1和t2,怎么办呢?这个时候,就要使用类型限定,对类型变量T设置限定(bound)来做到这一点。
我们知道,所有实现Comparable接口的方法,都会有compareTo方法。所以,可以对<T>做如下限定:
public static <T extends Comparable> T get(T t1,T t2) { //添加类型限定 if(t1.compareTo(t2)>=0); return t1; }
类型限定在泛型类、泛型接口和泛型方法中都可以使用,不过要注意下面几点:
(1)不管该限定是类还是接口,统一都使用关键字 extends
(2)可以使用&符号给出多个限定,例如
public static <T extends Comparable&Serializable> T get(T t1,T t2)
(3)如果限定既有接口也有类,那么类必须只有一个,并且放在首位置,例如
public static <T extends Object&Comparable&Serializable> T get(T t1,T t2)
参考
https://docs.oracle.com/javase/tutorial/java/generics/types.html
http://blog.csdn.net/lonelyroamer/article/details/7864531