目录
使用泛型机制编写的程序代码要比哪些杂乱地使用Object变量,然后再进行强制类型转换地代码具有更好的安全性和可读性。
以上摘自《Java核心技术卷一》
在谈泛型的定义之前,我先举一个简单又真实的例子:如果我想定义一个容器,在容器中放同一类的事物,理所当然嘛。但是在没有泛型之前,容器中默认存储的都是Object类型,如果在容器中增加不同类型的元素,都将会被接收,在概念上就不太符合了。关键是放进去不同元素之后,会造成一个很严重的情况:在取出元素并对里面的元素进行对应操作的时候,就需要复杂的转型操作,搞不好还会出错,就像下面这样:
//原生类型
ArrayList cats = new ArrayList();
cats.add(new Dog());
cats.add(new Cat());
for (int i = 0; i < cats.size(); i++) {
//下面语句类型强转会发生ClassCastException异常
((Cat) cats.get(i)).catchMouse();
}
而泛型又是怎么做的呢?通过尖括号<>
里的类型参数来指定元素的具体类型。
ArrayList<Dog> dogs = new ArrayList<>();
dogs.add(new Dog());
dogs.add(new SkyBarking());
dogs.add(new Snoopy());
dogs.add(new Cat());//编译不通过
//向上转型,另外两个是Dog的子类对象
for(Dog d:dogs){
System.out.println(d);
}
至此,泛型优点显而易见:
妙啊,从中我们可以体会泛型的理念:泛型只存在编译器,宁可让错误发生在编译期,也不愿意让程序在运行时出现类型转换异常。 因为bug发生在编译期更容易去找到并修复。 除此之外:
再次强调,所谓泛型,即参数化类型,就是只有在使用类的时候,才把类型确定下来,相当的灵活。
class Element<T>{
private T value;
Element(T value){
this.value = value;
}
public T getvalue() {
return this.value;
}
}
<>
扩起,放在类名后面。Element<String> element = new Element<>("天乔巴夏");
System.out.println(element.getvalue());
class ParaMethod {
public static <T> T getMiddle(T[] a) {
return a[a.length/2];
}
}
int m = ParaMethod.getMiddle(new Integer[]{1,2,3,4,5});
//返回Integer类型,自动拆箱
System.out.println(m);//3
我们上面讲到,泛型拥有足够的灵活性,意味着我传啥类型,运行的时候就是啥类型。但是,实际生活中,我要是想对整数类型进行操作,不想让其他类型混入,怎么办呢?对了,加上类型限定。
public static <T extends Number> T getNum(T num) {
return num;
}
修饰符 <T extends 类型上限> 返回类型 方法名 参数列表
,如上表示对类型变量的上限进行限定,只有Number及其子类可以传入。implements
关键字呢?答案是:否!接口依旧也是extends
。public static <T extends Comparable & Serializable> T max(T[] a) {
if (a == null || a.length == 0) return null;
T maximum = a[0];
for (int i = 1; i < a.length; i++) {
if (maximum.compareTo(a[i]) < 0) maximum = a[i];
}
return maximum;
}
<T extends 类&接口>
。<T extends Object>
。使用泛型类而不指定具体类型,这样的泛型类型就叫做原生类型(raw type),用于和早期的Java版本向后兼容,毕竟泛型JDK1.5之后才出呢。其实我们在本篇开头举的例子就包含着原生类型,ArrayList cats = new ArrayList();
。
ArrayList cats = new ArrayList();//raw type
它大致可以被看成指定泛型类型为Object的类型。
ArrayList<Object> cats = new ArrayList<Object>();
注意:原生类型是不安全的!因为可能会引发类型转换异常,上面已经提到。所以我们在使用过程中,尽量不要使用原生类型。
我们通过下面几个例子,来详细总结通配类型出现的意义,以及具体的用法。
如果我想定义一个方法,让它接收一个集合,不关注集合中元素的类型,并把集合中的元素打印出来,应该怎么办呢?
上面谈到泛型,你可能会这样写,让方法接收一个Object的集合,这样子你传进来啥我都接,完成之后美滋滋,一调试就不对了:
public static void print(ArrayList<Object> arrayList){
//错误!:arrayList.add(5);
for(int i = 0;i< arrayList.size();i++){
System.out.println(arrayList.get(i));
}
}
ArrayList<Integer> arr = new ArrayList<>();
print(arr);
究其原因:Integer是Object的子类的确没错,但是ArrayList
public static void print(ArrayList<?> arrayList)
?
表示接收所有的类型,可以看成是? extends Object
,这个就是我们即将要说的受限通配的格式了,非受限通配就是以Object为上限的通配,可不是嘛。?
时,由于类型的不确定,你不能够调用与对象类型相关的方法,就像上面的arrayList.add(5);
就是错误的。如果我想定义一个方法,让它接收一个整数类型的集合,应该怎么办呢?
public static void operate(ArrayList<Number> list){
/*operate a List of Number*/
}
/* 调用方法 */
ArrayList<Integer> arr = new ArrayList<>();
operate(arr);
上面的这个错误,想必你不会再犯,因为ArrayList
public static void operate(ArrayList<? extends Number> list){
/*operate a List of Number*/
}
?extends T
,表示T或者T的子类型。说完了上面两个,第三个我就不卖关子了,直接写上它的定义格式:? super T
,表示T或者T的父类型。
public static <T> void show(ArrayList<T> arr1,ArrayList<? super T>arr2){
System.out.println(arr1.get(0)+","+arr2.get(0));
}
ArrayList<Number> arr1 = new ArrayList<>();
ArrayList<Integer> arr2 = new ArrayList<>();
//编译出错
show(arr1,arr2);
以上将会编译错误,因为限定show方法中第二个参数的类型必须时第一个参数类型或者其父类。
泛型仅仅存在于编译,一但编译器确认泛型类型的安全性,就会将它转换原生类型。
当编译泛型类、接口或方法时,编译器会用Object代替泛型类型。以上面的例子举例:
Element<String> element = new Element<>("天乔巴夏");
System.out.println(element.getvalue());
将会变成:
Element element = new Element("天乔巴夏");
System.out.println((String)element.getvalue());
public static void operate(ArrayList<? extends Number> list){
/*operate a List of Number*/
}
将变成下面这样:
public static void operate(ArrayList<Number> list){
/*operate a List of Number*/
}
ArrayList<Number> arr1 = new ArrayList<>();
ArrayList<Integer> arr2 = new ArrayList<>();
System.out.println(arr1 instanceof ArrayList);//true
System.out.println(arr2 instanceof ArrayList);//true
可以看到,虽然ArrayList<Number>
和ArrayList<Integer>
是两种类型,但是由于泛型在编译器进行类型擦除,它们在运行时会被加载进同一个类,即ArrayList类。所以下面这句将会编译出错。
System.out.println(arr1 instanceof ArrayList<Number>);//编译出错
T t = new T();//错误
//错误:E[] elements = new E[5];
E[] elements = (E[])new Object[5];
//可以通过类型转换规避限制,但仍会导致一个unchecked cast警告,编译器不能够确保在运行时类型转换能否成功。
ArrayList<String>[] list = new ArrayList<String>[5];//错误
class Test<T> {
public static void m(T o1) {//错误
}
public static T o1;//错误
static {
T o2;//错误
}
}
本文若有叙述不当之处,还望评论区批评指正。
参考资料:
《Java核心结束卷一》、《Java语言程序设计与数据结构》
泛型就这么简单
https://www.programcreek.com/category/java-2/generics-java-2/
原文:https://www.cnblogs.com/summerday152/p/12190011.html