为什么要使用泛型?
第一是泛化。可以用T代表任意类型Java语言中引入泛型是一个较大的功能增强不仅语言、类型系统和编译器有了较大的变化,以支持泛型,而且类库也进行了大翻修,所以许多重要的类,比如集合框架,都已经成为泛型化的了,这带来了很多好处。
第二是类型安全。泛型的一个主要目标就是提高Java程序的类型安全,使用泛型可以使编译器知道变量的类型限制,进而可以在更高程度上验证类型假设。如果不用泛型,则必须使用强制类型转换,而强制类型转换不安全,在运行期可能发生ClassCast
Exception异常,如果使用泛型,则会在编译期就能发现该错误。
第三是消除强制类型转换。泛型可以消除源代码中的许多强制类型转换,这样可以使代码更加可读,并减少出错的机会。
第四是向后兼容。支持泛型的Java编译器(例如JDK1.5中的Javac)可以用来编译经过泛型扩充的Java程序(Generics
Java程序),但是现有的没有使用泛型扩充的Java程序仍然可以用这些编译器来编译。
这里的T,?,K,V都是泛型,不特指某个类型,可以通过一些extends,super方式限定类型。
T泛型一般用于类的构造中。
//构造个Test类,T extends Colleciton表面使用构造方法传入的参数必须是Collection或者Collection的子类。
public class Test <T extends Collection> {
T t ;
//获取传入的参数,并赋值给t
public Test(T t){
this.t = t;
}
}
?泛型用于参数传递。
? | ? extends | ? super | |
---|---|---|---|
说明 | ?不指定默认是Object。 | extends用于表示数据类型是该类的子类或者该类。 | super用于表示数据类型是该类或者该类的父类。 |
配合extends用法:
//这里指定传入的List集合的元素必须是Integer的子类或者Integer;
public static void get(List<? extends Integer> test){
}
调用
传入Integer,可以不指定Integer,默认Integer。
传入String,编译器不让通过
如果不用extends,super限定类型,那么默认是Object。因为Object是所有对象的父类。
public static void get(List<?> test){
}
调用默认是Object
也可以传入其他类型,比如Integer
配合super的用法
//必须是Integer的父类或者Integer
public static void get(List<? super Integer> s){
}
默认是Integer
可以传入Integer的父类,如Object
T和?的主要区别就是,T是用于类中的,因为它能当成给类对象进行修饰,用T来指代传入的数据类型。而?不能,他不能以?t的形式出现,它用来限定传入参数的数据类型,而T不能。
K,V用来指定键值对对象的泛型。如HashMap
//前面的<K,V>用来指定数据类型,后面的<K,V>指代实际的HashMap的键值对的类型。
public static <K extends String,V extends Integer>HashMap<K,V> test() {
HashMap<String, Integer> stringIntegerHashMap = new HashMap<>();
stringIntegerHashMap.put("123",123);
//由于实际是K,V类型,所以要强制转换。
return (HashMap<K, V>) stringIntegerHashMap;
}
接收如下,由于对K,V进行了限制,K是String或String的子类,V是Integer或Integer的子类,于是编译器能自动判别接收的类型。能够以String,Integer的类型接收HashMap。而不是以K,V接收。
那么可能有人有疑问了,这里的K,V能否用?,?来替代呢?
答案是肯定的,可以。因为?也是用来限定参数数据类型的。那么上面的写法可以用下面的写法来替代。
public static HashMap<? extends String,?extends Integer> test() {
HashMap<String, Integer> stringIntegerHashMap = new HashMap<>();
stringIntegerHashMap.put("123",123);
return stringIntegerHashMap;
}
接收如下
可以看到,明明我实际已经是传递String,Integer类型了,但是接收的时候它还是当做<?extends String,? extends Integer>接收。
直接使用String,Integer接收还需要强制转换如下
因此,在键值对中的写法,鼓励还是使用K,V代指泛型。
而以List<>的泛型写法如下
public static <T extends String>ArrayList<T> test() {
return (ArrayList<T>) new ArrayList<String>();
}
接收如下
而以?的写法定义和接收如下
public static ArrayList<? extends String> test() {
return new ArrayList<String>();
}
接收
可以看到,T,K,V泛型可以当做是已经确定的了的泛型,只要返回的参数符合其类型,那么编译器就正确识别并接收。而?是不确定的,只有在作为参数接收的时候,编译器能识别,而作为返回值时,编译器无法识别返回值的具体类型,需要进行强制转换。
总结:使用泛型是为了减少类型之间的强制转换,让编译器能够在未编译时就能知道用户的参数定义是否正确,而不是在运行阶段才能发现并运行异常。