一、认识泛型
首先提出一个问题:为什么有泛型?
设计背景:集合容器在设计阶段/声明阶段不能确定该容器实际存的是什么类型的对象,所以在JDK1.5之前统一将容器中的元素设计为Object,在JDK1.5之后使用泛型来解决。
将元素的类型设计成一个参数,这个参数就叫做泛型。例如:interface Collection<E> extends Iterable<E>其中的E就是泛型,根据传入类型来定。
泛型:就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用时确定(例如继承实现某某接口,声明变量,创建对象)。
一个类继承了父类,那么就能通过它的父类找到对应的子类,但是不能通过其他类来找到具体要找的和这个类。
好处:使用泛型能够规范集合中的元素,避免了强转出现异常的情况,没有实现泛型前是类型不安全的。
重要点:JDK1.5改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持。若是不指定类型也可以默认是Object。
JDK1.7新特性:类型推断
//当我们创建实例时,new后面不需要设置指定类型了(前面一定要有) Map<String,Integer> map = new HashMap<>();
二、自定义泛型结构
自定义泛型举例
class MyGeneric<T>{ private String name; private T Order; public void setOrder(T order) { Order = order; } public T getOrder() { return Order; } }
这里T作为泛型,等待创建实例传入指定的类型参数,并且该类属性也能够使用该泛型。
继承泛型类两种方式
方式一:不属于泛型类,直接确定类型
//当子类不使用泛型时,其继承的类需要明确类型 class Type1 extends MyGeneric<String>{//这里使用的上面自定义例子的类 }
方式二:属于泛型类,根据传过来的类型决定
//可以通过创建Type2实例时将类型也传递给MyGeneric class Type2<T> extends MyGeneric<T>{ }
自定义泛型注意点
1、泛型类可以有多个参数,多个参数要一起放在<>中,例如<T,E,K>。
2、泛型类的构造器不需要使用<E>,和往常一样即可。
3、使用泛型类若是不指定类型,对应类型默认为Object。
4、若泛型类是一个接口或抽象类,不能创建泛型类的对象。
5、泛型指定只能使用引用数据类型,若是想要表示基本数据类型可用包装类代替。
6、异常类不能指定泛型。
三、泛型不同使用情境
泛型表示接口 interface MyGeneric<E>{ E get(); }
一般泛型接口常用于生成器,即对象工厂,一种专门用来创建类的工厂对象。
泛型方法
泛型方法:在方法中出现泛型的结构,泛型参与类的泛型参数没有任何关系。
情况一:使用类中的泛型
class MyGeneric<T>{ private String name; private T Order; //这里的T与创建实例时传入的类型一致 public List<T> getList(List<T> arr){ return arr; } }
情况二:若是单独在方法中使用泛型,则需要在返回参数之前加上类似于<E>标记
class MyGeneric<T>{ private String name; private T Order; //为了让E不表示一个类而是一个参数则需要在返回参数前加<E> public <E> List<E> copy(List<E> arr){ return arr; } }
不加标记会将E识别为一个类,从而找不到该类出现编译错误:
四、泛型继承上的体现
①类A是类B的超类,G<A> 和 G<B> 二者不具备子父类关系,二者是并列关系
②类A是类B的父类,A<G> 是 B<G> 的父类
总结:对于具有子父类关系的泛型类,其泛型应该一致,否则不具备子父类关系。
五、通配符使用
通配符为:?
通过使用?通配符能够解决之前G<A> 和 G<B>的问题,这两者共同的父类为G<?>
运用到方法中:
import java.util.*; public class Main { public static void main(String[] args) { List<String> list = new ArrayList<>(); List<Integer> list2 = new ArrayList<>(); traversal(list); traversal(list2); } //可遍历任意list<A> List<B> public static void traversal(List<?> list){ Iterator<?> iterator = list.iterator(); while(iterator.hasNext()){ System.out.println(iterator.next()); } //或者 // for (Object o : list) { // System.out.println(o); // } } }
注意点:例如上面List使用?不能进行add()的添加操作,获取值也只能获取到Object类型。
@Test public void test(){ List<String> list1 = new ArrayList<>(); //给list1添加两个String字符串 list1.add("AA"); list1.add("BB"); List<?> list2 = null; list2 = list1; //添加(写入):对于List<?> 就不能向其内部添加数据 //除了添加null之外 // list2.add("AA"); // list2.add('?');//两个都会进行报错 list2.add(null);//添加null值是允许的 //获取读取:允许读取数据,读取的数据类型为Object Object o = list2.get(0); System.out.println(o);//AA }
有限制条件的通配符使用
<? extends Number>:只允许泛型为Number及Number子列的引用调用。 <? super Number>:只允许泛型为Number及Number父类的引用调用。 extends:使用时只能使用对应类及其子类。 super:使用时只能使用对应类及其父类。 例子如下: public static void main(String[] args) { List<? extends Person> list = new ArrayList<>(); List<? super Person> list2 = new ArrayList<>(); List<Person> list3 = new ArrayList<>(); List<Student> list4 = new ArrayList<>(); List<Object> list5 = new ArrayList<>(); //赋值操作 list = list3; list = list4; list = list5;//无法赋值,Object是Person父类 list2 = list3; list2 = list4;//无法赋值,Student是Person子类 list2 = list5; //获取对应值 Person person = list.get(0);//由于泛型范围为Person及其子类,所以返回一个最大父类Person Object object = list2.get(0);//由于泛型范围为Person及其父类,所以返回一个最大父为Object }
注意其中引用赋值范围以及获取对应泛型参数值的范围!!!
六、泛型中E、T、K等含义
其实可以使用任何合法的Java标识字符串,习惯用法是单一的字母表示,为了好辨识某些泛型的含义,特使用如下几个字母:
E:可表示Element,一般在集合中使用,集合中存放元素。
T:可表示Type,Java类。
K:可表示Key,作为键,Map中使用到。
V:可表示Value,作为值,Map中使用到。
N:Number,数值类型。
?:表示不确定的java类型。
参考文章
[1]. Java泛型中E、T、K、V等的含义