1. 什么是泛型
泛型的本质是类型参数化,也就是对把类型也进行参数化,是对java语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类。可以把类型参数看作是使用参数化类型时指定的类型的一个占位符,就像方法的形式参数是运行时传递的值的占位符一样。
在定义泛型类或声明泛型类的变量时,使用尖括号来指定形式类型参数。形式类型参数与实际类型参数之间的关系类似于形式方法参数与实际方法参数之间的关系,只是类型参数表示类型,而不是表示值。
下面是 java.util.Map 接口的定义的摘录:
public interface Map<K, V> {
public void put(K key, V value);
public V get(K key);
}
2. 泛型什么时候引入的
Java在1.5之前一直没有相对应的功能。一个集合可以放任何类型的对象,相应地从集合里面拿对象的时候我们也 不得不对他们进行强制得类型转换。java 1.5通过引入泛型,它允许指定集合里元素的类型,这样你可以得到强类型在编译时刻进行类型检查的好处。
3. 泛型的优点
能够在编译时期检测出错误而不是运行时期,提高了程序的可靠性。
不需要强制类型转换就可以在ArrayList列表中提取出元素。
4.泛型的命名
推荐的命名约定是使用大写的单个字母名称作为类型参数。并反映了大多数泛型类将具有少量类型参数的假定。对于常见的泛型模式,推荐的名称是:
K —— 键,比如映射的键。
V —— 值,比如 List 和 Set 的内容,或者 Map 中的值。
E —— 异常类。
T —— 泛型。
5. 泛型分类
a. 类型通配符
void printList(List<?> l) {
for (Object o : l)
System.out.println(o);
}
上面代码中的问号是一个类型通配符。它读作“问号”。List<?> 是任何泛型 List 的父类型,所以您完全可以将 List<Object>、List<Integer> 或 List<List<List<String>>> 传递给 printList()。
b 泛型类
class MyStack<T>{
private ArrayList<T> list=new ArrayList<T>();
public int getSize(){
return list.size();
}
public void push(T o){
list.add(o);
}
//取得栈顶元素而不出栈
public T peek(){
return list.get(list.size()-1);
}
//出栈操作
public T pop(){
T o=list.get(list.size()-1);
list.remove(list.size()-1);
return o;
}
public boolean isEmpty(){
return list.isEmpty();
}
}
泛型类的定义即在类名后用尖括号写上泛型符号如T、E(可理解为占位符)等,注意:泛型类的构造方法为 MyStack() 而不是MyStack<T>()
c 泛型方法
下面代码中的 ifThenElse() 方法,根据它的第一个参数的布尔值,它将返回第二个或第三个参数:
public <T> T ifThenElse(boolean b, T first, T second) {
return b ? first : second;
}
之所以声明泛型方法,一般是因为您想要在该方法的多个参数之间宣称一个类型约束。
您可以调用 ifThenElse(),而不用显式地告诉编译器,您想要 T 的什么值。编译器不必显式地被告知 T 将具有什么值;它只知道这些值都必须相同。编译器允许您调用下面的代码,因为编译器可以使用类型推理来推断出,替代 T 的 String 满足所有的类型约束:
String s = ifThenElse(b, "a", "b");
类似地,您可以调用:
Integer i = ifThenElse(b, new Integer(1), new Integer(2));
但是,编译器不允许下面的代码,因为没有类型会满足所需的类型约束:
String s = ifThenElse(b, "pi", new Float(3.14));
为什么您选择使用泛型方法,而不是将类型 T 添加到类定义呢?(至少)有两种情况应该这样做:
当泛型方法是静态的时,这种情况下不能使用类类型参数。
当 T 上的类型约束对于方法真正是局部的时,这意味着没有在相同类的另一个方法签名中使用相同类型 T 的约束。通过使得泛型方法的类型参数对于方法是局部的,可以简化封闭类型的签名.
c. 有限制类型
在前一屏泛型方法 的例子中,类型参数 V 是无约束的或无限制的类型。有时在还没有完全指定类型参数时,需要对类型参数指定附加的约束。
考虑例子 Matrix 类,它使用类型参数 V,该参数由 Number 类来限制:
public class Matrix<V extends Number> { ... }
编译器允许您创建 Matrix<Integer> 或 Matrix<Float> 类型的变量,但是如果您试图定义 Matrix<String> 类型的变量,则会出现错误。类型参数 V 被判断为由 Number 限制 。在没有类型限制时,假设类型参数由 Object 限制。这就是为什么前一屏 泛型方法 中的例子,允许 List.get() 在 List<?> 上调用时返回 Object,即使编译器不知道类型参数 V 的类型。
6.泛型的限制
a. 除了异常类型、枚举或匿名内部类以外,任何类都可以具有类型参数。
b. 泛型仅存在于编译时期,编译期间JAVA将会使用Object类型代替泛型类型,在运行时期不存在泛型;且所有泛型实例共享一个泛型类
public class Main{
public static void main(String[] args){
ArrayList<String> list1=new ArrayList<String>();
ArrayList<Integer> list2=new ArrayList<Integer>();
System.out.println(list1 instanceof ArrayList);//true
System.out.println(list2 instanceof ArrayList);//true
//System.out.println(list1 instanceof ArrayList<String>);//编译错误,不存在此类型
}
}
虽然list1和list2属于不同的类型,但是JVM加载的类仅ArrayList,各泛型实例共享这个类;
泛型的4个限制:(1)不能new T()
(2)不能创建泛型数组:new T[];
但是可以使用强制类型转换的方法,不过编译器会给出一个警告。
T[] arr=(T[])new ArrayList[10];
(3)在静态环境下不能使用泛型类型的参数
如 静态方法、静态变量、静态代码块都不能用泛型类型,原因在于各泛型实例共享一个类,而静态成员时随着类的加载就存在了的。
(4)异常类不能是泛型的。
总结:JDK1.5之后,很多原始类型都被重写成了泛型类或者泛型接口,如Comparable<T>接口、ArrayList<T>以及集合中其他很多的方法也都是带泛型类型的;泛型类型由于在存在类型消除,因此其兼容原始类型,如前面定义的泛型类MyStack, MyStack与MyStack<Object>表面上看起来一样,实际上还是有区别的,前者是原始类型,后者是泛型的实例化。