java的泛型并非新的特性,在JDK1.5时就有的操做,其实咱们一直在使用泛型,常见的List,Map中都有它的身影java
那它有什么用处呢?以咱们最经常使用的ArrayList为例安全
public void test1() { ArrayList arrayList=new ArrayList(); arrayList.add("张三"); arrayList.add("李四"); arrayList.add("王五"); for (Object o : arrayList) { String name= (String) o; System.out.println(name); } }
来看它的get方法返回类型ide
咱们都知道,List中能够存储数据,那么上面的代码看起来没有问题,运行也没有问题学习
可是,若是咱们往list添加个int类型的数据,那么在下面强制转换输出时,就会报出ClassCastException
异常测试
缘由也很简单,咱们不能把一个int类型的变量强转为String类型,那么问题也很明显了,List的安全性idea
有解决方法吗,固然有,对于每一个类型单首创建对应的List,每一个List中只能存放对应的类型,这样虽然能解决问题,可是重复了大量相同代码,重复造轮子3d
因此在JDK1.5的时候,出现了一个新特性 : 泛型(genericity)code
程序不能肯定运行时的类型,可是使用Object来表示类型又太过笼统对象
好比北京大学旁边的理发店,理发店办活动,北京大学的学生来理发有优惠,那么理发店如何来指定那些人有优惠呢?blog
人类有优惠?范围太大.
具体的某我的有优惠?那么理发店把北京大学全部人名打印贴在墙上?这又太过于具体.
因此JDK想指定一些不肯定的类型,可是又不想太过于笼统,这就使用到了泛型
泛型本质是将类型参数化,经过传入的类型来判断当前运行的类型,而泛型又分为泛型类、泛型接口、泛型方法
咱们以常见的ArrayList为例:
public class ArrayList<E> ...{ public boolean add(E e) {...} public E get(int index) {...} }
咱们能够看到,在类名后面添加了一对尖括号<>里面有一个大写字母E 这就表示ArrayList为一个泛型类,括号中能够写多个泛型
在实例化时能够经过new ArrayList<>后面的尖括号中,来添加这个List中使用的类型
ArrayList<String> arrayList=new ArrayList<String>();
很明显,当咱们设置完List的使用类型后,咱们add Int类型的方法出现了异常
idea提示咱们所需的类型是一个String类型,而咱们提供的是一个int类型,这也说明了泛型具备检查的类型的功能
在JDK1.7以后,若是接收类型中显式设置了类型,那么实例化对象能够不用写具体的类型,只须要写一对尖括号便可
ArrayList<String> arrayList=new ArrayList<>();
那么设置完泛型后,咱们在来看get方法的返回类型
这里能够看到,在咱们设置完List的泛型后,返回的类型已经为具体的String类了
那么它是如何实现动态的类型变化的呢?
咱们来本身建立一个泛型类,一切就都知道了
医院类
public class Hospital<T> { /** * 输出动物的信息方法 * @param t */ public void print(T t){ System.out.println(t); } /** * 给宠物打疫苗方法 * @return T */ public T vaccine(T t){ System.out.println("给宠物打疫苗"); return t; } }
猫类
public class Cat { private int age; private String name; //省略构造,get,set,toString方法 }
固然,上面这个例子可能不是太好,这种状况彻底能够抽出父类Animal类进行多态的实现,可是咱们如今仅仅为了学习泛型
那么上面医院类Hospital的< T > 以及下面两个方法中的T是什么意思呢?
咱们能够想象为,当实例化Hospital医院类时候,经过new Hospital< Cat>(); 将猫Cat类传入到Hospital类中,那么在这个已经实例化的Hospital对象中,全部的T都表明为传入的Cat类,简单点说就是,传入啥类型T就是啥类型
那么T是java的关键字吗,并非,咱们能够任意起名,就像和给变量起名同样,可是泛型有些规范咱们要尽可能遵照
java泛型字母表明意思
E | Element (在集合中使用,由于集合中存放的是元素) |
---|---|
T | Type(Java 类) |
K | Key(键) |
V | Value(值) |
N | Number(数值类型) |
public void test1() { Hospital<Cat> catHospital = new Hospital<>(); catHospital.print(new Cat(5,"小花")); }
咱们能够看到,当设置完泛型后,咱们能够动态实现对于一个类的不一样实现
声明泛型的类和普通的类没有太大的区别,一样可使用继承
可能会出现子类保留,指定,新增泛型的状况
对于上面这几种状况直接概况一下
什么意思呢?咱们来看
这种状况在实例化Son对象时,设置AB的泛型类型,那么Son中和Father中的类型就会是设置的类型
好比设置为String,Integer,那么子类和父类中的泛型类型都为String和Integer
//父类 public class Father<A,B> { } //子类 public class Son<A,B> extends Father<A,B>{ public void print(A a,B b){ System.out.println("Son-A的类型:"+a.getClass()); System.out.println("Son-B的类型:"+b.getClass()); } } //测试 public void test1() { Son<String, Integer> son = new Son<>(); son.print("",1); } //结果 Son-A的类型:class java.lang.String Son-B的类型:class java.lang.Integer
除了子类保留父类,还能够直接指定父类泛型类型
//父类 public class Father<A,B> { } //子类 在这里直接设置了父类泛型类型 ↓ public class Son<A,B> extends Father<Integer,B>{ public void print(A a,B b){ System.out.println("Son-A的类型:"+a.getClass()); System.out.println("Son-B的类型:"+b.getClass()); } }
那么在实例化子类时即便设置new Son<String,String>(); 那么父类的两个泛型类型只会是Integer和String,由于泛型A已经在子类中指定了
还有一种没有保留父类泛型类型
//不保留也不指定父类泛型类型 ↓ 去掉这里的 <A,B> public class Son<A,B> extends Father{ public void print(A a,B b){ System.out.println("Son-A的类型:"+a.getClass()); System.out.println("Son-B的类型:"+b.getClass()); } }
若是不保留父类泛型类型,也不指定父类泛型类型,那么泛型类型默认为Object至关于extends Father<Object,Object>
若是部分保留,除非子类须要的话,那么子类只须要声明保留的泛型便可
泛型接口 和泛型类继承一致,这里再也不赘述
泛型方法不必定存在泛型类或泛型接口中,只对方法而言,传入的参数类型不肯定
例以下面这个getMax方法,若是是Integer返回最大值,若是是String根据ASII码返回最大值,假设传入List中的类型为String或Integer
public <E> E getMax(List<E> es){ if(es==null || es.size()==0) return null; E e = es.get(0); if (e instanceof Integer) { //获取最大值操做... } if(e instanceof String){ //获取最大值操做... } return null; }
咱们能够看到,传入的参数并不肯定,这种方法能够称之为泛型方法,泛型方法怎么声明呢?
//在返回类型前面加上<X> X能够换成任何字母,可是尽可能符合规范 //那么在返回类型,传入参数类型,方法体中均可以使用这个定义泛型 public <E> List<E> get(E[] arrays){ }
?表示未知类型,
例如List<?> 那么任何类型的List均可以赋值给这个List,可是注意,并不能直接往这个List中添加数据
也有个例外,添加null能够
public void test1() { List<?> list=new ArrayList<>(); // list.add("小明"); 报错 须要的类型为 ? 而咱们传入类型为String //可是下面这些赋值都没有问题 List<String> stringList = new ArrayList<>(); List<Integer> integerList = new ArrayList<>(); list = stringList; list = integerList; }
List<?> 的get方法,获取的类型是Object
通配符集合能够做为方法的形参,来接收List集合中不肯定的类型,例以下面这个例子
//这里使用迭代器来进行遍历 public void print(List<?> list){ ListIterator<?> listIterator = list.listIterator(); while (listIterator.hasNext()){ Object next = listIterator.next(); System.out.println(next); } }
这里有须要注意的点,例如X类为Y类的父类,泛型genericity 简称G G< Y >并不能直接赋值给G< X >,例以下面这个例子
String为Object的子类,为何不能赋值给Object的集合呢?
这里咱们须要将它们想象成两个类,虽然在运行中只是一个类,咱们能够想象为一个类只为Object提供服务,另外一个只为String提供服务,虽然Object和String有继承关系,可是它们两个集合并无任何关系,只是功能相同而已并不存在List< String>继承List< Object>的关系
就像请俩技师一个给儿子捏脚一个给爸爸捏脚,虽然儿子和爸爸有继承关系,可是两个技师没有任何关系(假设)
咱们前面看到了使用?通配符能够传入任何类型,可是使用Object做为形参同样能够完成,因此为了安全性,通配符还提供了一系列限制条件
<? extends xxx>和<? super xxx>
先来讲<? extends xxx> 能够匹配继承于xxx或者xxx类型 小于等于该类的类型
咱们使用经常使用的List来测试,建立3个类,类中什么都没有,仅是来测试通配符
Animal动物类为父类->Cat猫类继承Animal类-> XiaoHuaCat小花猫类继承Cat类
@Test public void test1() { //设置通配符限制条件 List<? extends Cat> list = new ArrayList<>(); List<Cat> cats = new ArrayList<>(); List<XiaoHuaCat> xiaoHuaCats = new ArrayList<>(); List<Animal> animals = new ArrayList<>(); list = cats; list = xiaoHuaCats; //这条赋值语句报错,由于不是继承Cat或Cat类型 list = animals; }
那么它get方法返回的类型为Cat类,由于这已是在这个集合中最大的父类了,它在这个集合中不可能再有父类了
再来讲<? extends xxx> 能够匹配xxx继承的类类型或者xxx类型 大于等于该类的类型
@Test public void test1() { List<? super Cat> list = new ArrayList<>(); List<Cat> cats = new ArrayList<>(); List<Animal> animals = new ArrayList<>(); List<XiaoHuaCat> xiaoHuaCats = new ArrayList<>(); list = cats; list = animals; //这条语句会报错,由于最小的类型就是Cat类了,而XiaoHuaCat是继承于Cat的 list = xiaoHuaCats; }
它的get方法返回类型为Object ,由于不管哪一个类,它的祖先类必定是Obejct类,由于父类对象引用子类对象是容许的,因此get的是全部类的父类
本文仅我的理解,若是有不对的地方欢迎评论指出或私信,谢谢٩(๑>◡<๑)۶