Java-泛型
Java泛型
java的泛型并不是新的特性,在JDK1.5时就有的操作,其实我们一直在使用泛型,常见的List,Map中都有它的身影
那它有什么用处呢?以我们最常用的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方法返回类型
我们都知道,List中可以存储数据,那么上面的代码看起来没有问题,运行也没有问题
但是,如果我们往list添加个int类型的数据,那么在下面强制转换输出时,就会报出ClassCastException
异常
原因也很简单,我们不能把一个int类型的变量强转为String类型,那么问题也很明显了,List的安全性
有解决方法吗,当然有,对于每个类型单独创建对应的List,每个List中只能存放对应的类型,这样虽然能解决问题,但是重复了大量相同代码,重复造轮子
所以在JDK1.5的时候,出现了一个新特性 : 泛型(genericity)
程序不能确定运行时的类型,但是使用Object来表示类型又太过笼统
比如北京大学旁边的理发店,理发店办活动,北京大学的学生来理发有优惠,那么理发店如何来指定那些人有优惠呢?
人类有优惠?范围太大.
具体的某个人有优惠?那么理发店把北京大学所有人名打印贴在墙上?这又太过于具体.
所以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的
我们可以想象为,当实例化Hospital医院类时候,通过new Hospital
那么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,"小花"));
}
我们可以看到,当设置完泛型后,我们可以动态实现对于一个类的不同实现
继承关系
声明泛型的类和普通的类没有太大的区别,同样可以使用继承
可能会出现子类保留,指定,新增泛型的情况
对于上面这几种情况直接概况一下
- 如果子类没有保留父类泛型,那么父类泛型默认为Object
- 如果指定父类泛型的类型,那么父类泛型类型为指定的类型
- 如果子类保留了父类类型,那么泛型类型就为子类实例化时设置的类型
什么意思呢?我们来看
这种情况在实例化Son对象时,设置AB的泛型类型,那么Son中和Father中的类型就会是设置的类型
比如设置为Integer,String,那么子类和父类中的泛型类型都为Integer和String
//父类
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
String为Object的子类,为什么不能赋值给Object的集合呢?
这里我们需要将它们想象成两个类,虽然在运行中只是一个类,我们可以想象为一个类只为Object提供服务,另一个只为String提供服务,虽然Object和String有继承关系,但是它们两个集合并没有任何关系,只是功能相同而已,并不存在List
就像请俩技师一个给儿子捏脚一个给爸爸捏脚,虽然儿子和爸爸有继承关系,但是两个技师没有任何关系(假设)
通配符限制条件
我们前面看到了使用?通配符可以传入任何类型,但是使用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的尽量都是所有类的父类