意义和作用:
类型的参数化,就是可以把类型像方法的参数那样传递。这一点意义非凡。
泛型使编译器可以在编译期间对类型进行检查以提高类型安全,减少运行时由于对象类型不匹配引发的异常。
想要理解为什么引入泛型,就要知道没有泛型的麻烦。
我们知道现在的程序开发都是面向对象了,所以程序里会有很多各种类型的对象,对象多了肯定需要有某种类型的容器来装。所以就有了一些容器类型,比如数组、ArrayList、HashMap、TreeSet等。
对于数组,我们知道需要在使用时指定数组装的对象类型,如:
1 | Animal animal[]; |
而对于集合类型容器如ArrayList、HashMap、TreeSet等,它们不但是容器,还提供了一些方法对容器内对象的操作方法,如get,set,sort。这个时候就需要知道容器内放的是什么类型的对象,才能return或set。
正因为程序开发人员可能把任何类型的对象放进集合容器,所以这些容器在设计的时候只能默认设计成装Object类型对象。因为Java里Object是根类。
所以容器就成了类似这个样子:
1 | public class ListContainer { |
这样的话,根据多态,容器就能装任何类型的对象了。不过,取出对象时则需求进行强制类型转换,转换成实际的类型。但这样会有很多类型不安全问题,为什么呢?因为编译器没法帮忙做类型检查,导致代码在运行时易于出现ClassCastException异常。因此,类型安全问题只能由程序员自己来把关了,记住各种类型,避免类型转换错误。
1 | ListContainer myContainer = new ListContainer(); |
泛型出场:类型的参数化
利用泛型,重新设计:
1 | public class ListContainer<T> { |
这里ListContainer<Dog> myCon=new ListContainer<Dog>();
这种环境下,编译器就知道ListContainer容器是放Dog类型对象的。并进行类型安全检查。
1 | myCon.setObj(new Dog())//ok |
这样设计的容器在使用时编译器就可以帮忙做很大一部分的类型安全检查工作了,这就避免了很多运行时的ClassCastException异常,程序员也无需记住各种对象的类型和担心类型匹配问题了。同时大部分情况下也不用做类型强制转换工作了。
1 | ListContainer<String> myContainer = new ListContainer<String>(); |
当然泛型的<>里也可以放多个参数,如:
1 | public class MultiContainer<T,S> { |
有界泛型
看看这个泛型和多态的问题,Dog,Cat是Animal的子类:
1 | public void killAll(ArrayList<Animal> animals){...};//Animal容器 |
在这里看上去似乎多态不行了。
这里就要用到有界泛型:
在使用泛型时,我们会有这种需求:需要指定泛型的类型范围。有界类型就是在类型参数部分指定extends或super关键字,这里的extends也含有implements的功能,分别用上限或下限来限制类型范围,从而限制泛型的类型边界。例如:
1 | <T extends Animal>//限定T是Animal的子类 |
那么上面那个多态问题就变成:public void killAll(ArrayList<T extends Animal> animals){...};
解决了<T extends Object&Comparable&Serializable>
多个限定时我们可以使用&来进行分割,这时关键词只能使用extends。与多重继承类似,这里只有一个类其他都是接口。
泛型方法
有时,我们设计的方法可能其参数类型是不限定的。这种场景如果用重载方法的方式来做的话,算法重复,不是最好的方案。此时泛型方法就可以解决此类问题。
如Calculator的add方法:
1 | public static < N extends Number > double add( N a, N b ){ |
如果用重载来做的话,要很多重复代码了。