Java之泛型
就是允许在定义类,接口,方法时候使用类型形参,这个类型形参将在声明变量,创建对象,调用方法时候动态地
指定(即传入实际的类型参数,也可称为类型实参)。
public interface Map<T>{
// 静态方法,静态初始化块或者静态变量的声明和初始化中不允许使用类型形参
public static void test(T t){} // 发生错误
}
从泛型类派生子类
public class A1 extends Apple<String>{}
类型通配符
为了表示各种泛型的父类,我们需要使用类型通配符,类型通配符是一个问号(?),将一个问号作为类型实参传给
List集合,写作:List<?>(意思就是未知类型元素的List集合),这个问号被称为通配符,它的元素可以匹配任何
类型。
public void test<List<?> c){ // 可以接受任何类型的List集合的参数
for(int i=0;i<c.size()0;i++){
System.out.println(c.get(i));
}
}
设定类型通配符的上限
class Fruit{public String name;}
class Apple extends Fruit{}
class Peach extends Fruit{}
class Banana extends Fruit{}
public class Test{
public static void main(String[] args){
}
// 此时的这个通配符被限制在一定是Fruit的子类,这个Fruit称这个通配符的上限
public void displayName(List<? extends Fruit> fruits){
}
// 可以设置多个上限
public void displayName(List<? extends Fruit & java.io.Serializable> fruits){
}
}
泛型方法
就是在声明方法时定义一个或多个类型形参。
public class Test{
static<T> void print_array(T[] as,Collection<T> c){
for(T a:as){
c.add(o);
}
}
public static void main(String[] args){
Object[] oa = new Object[100];
Collection<Object> co = new ArrayList<>();
print_array(oa,co); // 方法中的泛型无须显式传入实际类型参数,编译器根据实参推断类型形参的值。
}
}
泛型方法和通配符的应用时机
1.泛型方法允许类型形参被用来表示方法的一个或多个参数之间的类型依赖关系,或者方法返回值与参数之间的
类型依赖关系。如果没有这样的类型依赖关系,就不应该使用泛型方法。如上面程序里的print_array方法就有
依赖关系。
2.类型形参只使用一次,类型形参T产生的唯一效果是可以在不同的调用点传入不同的实际类型。对于这种情况,
应该使用通配符。
泛型构造器
允许在构造器签名中声明类型形参。
class AA{
public<T> AA(T t){
System.out.println(t);
}
}
class BB<T>{ // Java7新增的菱形语法
public<T> BB(T t){
System.out.println("t参数的值:" + t);
}
}
public class Test{
public static void main(String[] args){
// 泛型构造器
new <String> AA("泛型构造器");
// Java7新增泛型构造器
BB<String> b = new BB<>(5);
}
}
设定通配符的下限
为了表达这种依赖关系,不管src集合元素的类型是什么,只要dest集合的类型与前者相同或者前者的父类
即可。
public class Test{
// 不管src集合元素的类型是什么,只要dest集合的类型与前者相同或者前者的父类即可
public static<T> T copy(Collection<? super T> dest,Collection<T> src){
T last = null;
for(T ele:src){
last = ele;
dest.add(ele);
}
return last;
}
public static void main(String[] args){
List<Number> li = new ArrayList<>();
Ii.add(5);
Integer last = copy(ln,li);
System.out.println(ln);
}
}
警告与错误:
泛型方法与重载方法的冲突
泛型既允许设定通配符的上限,也允许设定通配符的下限,从而允许在一个类中包含如下两个方法定义
public class Test{
public static<T> void copy(Collection<T> dest,Collection<? extends T> src>{};
public static<T> T copy(Collection<? super T> dest,Collection<T> src){};
public static void main(String[] args){
List<Number> ln = new ArrayList<>();
List<Number> li = new Arraylist<>();
copy(ln,li); // error!
}
}
上面的copy都可以匹配两个copy函数,从而产生了编译器无法确定哪行代码想调用哪个copy,会产生错误
擦除和转换
擦除:当一个具有泛型信息的对象赋给另一个没有泛型信息的变量时,所有在尖括号之间的类型信息都将被扔
掉。
class Apple<T extends Number>{
T size;
public Apple(){};
public Apple(T size){this.size = size};
public void setSize(T size){this.size = size;}
public T getSize(){return this.size;}
}
public class ErasureTest{
public static void main(String[] args){
Apple<Integer> a = new Apple<>(6); // 传入类型实参是Integer类型
Integer as = a.getSize(); // 现在的a的getSize()方法返回Integer对象
// 把a对象赋给Apple变量,丢失尖括号里的类型信息
Apple b = a;
// 因为Apple类的形参上限是Number,所以b值知道size是Number
Number size1 = b.getSize();
// 引起编译错误,因为b被编译器认为是Number,无法赋值给Integer
Integer size2 = b.getSize();
}
}
未经检查的转换:泛型可以直接把一个List对象赋给一个List<String>对象,编译器仅仅提示"未经检查的转换"警
告。
public class ErasureTest{
public static void main(String[] args){
List<Integer> li = new ArrayList<>();
li.add(6);
li.add(9);
List list = li;
// 引发"未经检查的转换"警告
List<String> ls = list;
// 只要访问ls里的元素,如下代码将引发运行异常
// 因为list的实际上引用的是List<Integer>,把他当String类型就出错
// System.out.println(ls.get(0));
}
}
泛型与数组
可以声明元素类型包含类型变量或类型形参的数组List<String>[]形式,但不能创建ArrayList<String>[10]的
对象。
List<String>[] lsa = new ArrayList[10];// 引发"未经检查的转换"警告
Object[] oa = (Object[])lsa;
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(3));
oa[1] = li;
String s = lsa[1].get(0); // 报错
泛型的重要设计原则:如果一段代码在编译时,没有提出"[unchecked]未经检查的转换"警告,则程序在运行
时不会引发ClassCastException异常。
基于这个原因,数组元素类型不能包含类型变量或类型形参。除非是无上限的类型通配符泛型数组。
List<?>[] lsa = new ArrayList<?>[10];
Object[] oa = (Object[])lsa;
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(3));
oa[1] = li;
// 下面代码引发ClassCastException异常
String s = (String)lsa[i].get(0);// 因为程序需要将lsa的第一数组元素的第一个集合强制转换为String类型
如果改为以下形式:
List<?>[] lsa = new ArrayList<?>[10];
Object[] oa = (Object[])lsa;
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(3));
oa[1] = li;
// 通过instanceof运算符来保证它的数据类型
if(lsa[i].get(0) instanceof String){
String s = (String)lsa[i].get(0); // 代码安全
}