一、泛型不具备继承性,但是数据具备继承性。
基础代码如下,这里已经写好了一个继承结构
public class GenericsDemo5 {
public static void main(String[] args) {
}
}
class Ye {}
class Fu extends Ye {}
class Zi extends Fu {}
首先我们先来演示前半句:泛型不具备继承性
。
在下面我们可以定义一个方法,然后在测试类中创建集合
public class GenericsDemo5 {
public static void main(String[] args) {
//创建集合的对象
ArrayList<Ye> list1 = new ArrayList<>();
ArrayList<Fu> list2 = new ArrayList<>();
ArrayList<Zi> list3 = new ArrayList<>();
}
public static void method(ArrayList<Ye> list) {
}
}
class Ye {}
class Fu extends Ye {}
class Zi extends Fu {}
下面就去调用 method()
,首先将 list1
放进去,可以发现代码并没有报错,表示我们可以传递 list1
过去,没有任何的问题。
method(list1);
但是当我们再传递 list2、list3
的时候,他就会报错。

报错的原因就是因为:泛型不具备继承性。
此时,泛型里面写的是什么类型,那么只能传递什么类型的数据,你传递 list2、list3
都不行,只有传递 list1
才行,因为 list1
中传递的是 <Ye>
,跟下面的形参是完全一致的,这个就是 泛型不具备继承性

但是数据具备继承性。
也就是说我们现在往 list1集合
中可以添加 Ye
、Fu
、Zi
的对象,这些添加的 ``Ye、
Fu、
Zi 的对象
就是数据。
list1.add(new Ye());
list1.add(new Fu());
list1.add(new Zi());
二、泛型的通配符
1)介绍
需求:定义一个方法,形参是一个集合,但是集合中的数据类型不确定。
public static void method(ArrayList<???> list) {
}
有同学就想了:既然集合里面的数据类型不确定,那就可以利用我们刚刚学的 泛型方法
解决,告诉你,完全是可以的。
// 泛型需要先定义,再使用:即先在修饰符后面定义 <E>,才能在形参中使用 E
public static<E> void method(ArrayList<E> list) {
}
此时在测试类中添加 list1、list2、list3
都没有问题
method(list1);
method(list2);
method(list3);
但是这种写法它会有一个小小的弊端:此时他可以接受任意的数据类型,例如 Ye Fu Zi
,甚至你定义的类不在继承结构中,例如 Student
,它也是可以传递过去的。
但在有的时候我们希望本方法虽然不确定类型,但是希望以后只能传递 Ye Fu Zi
。
此时我们可以就可以使用泛型的通配符,在Java中,泛型的通配符就是一个问号,表示不确定的类型。
但是它可以进行类型的限定,它的限定方式有两种:
? extends E
:表示可以传递E
或者E所有的子类类型
? super E
:表示可以传递E
或者E所有的父类类型
下面如果直接在泛型中写一个 <?>
,那么它跟 E
是差不多的,都表示不确定类型,只不过如果写 ?
的话,就不需要在前面再去定义了。
public static void method(ArrayList<?> list) {
}
如果泛型只写 ?
,还是表示所有的类型都可以传递过来。
但是如果使用 ? extends E
,那就表示 method方法
它能接收集合里面参数的类型是 ?
,即任意的,但是这个类型必须是 E
或者 E所有的子类类型
。
PS:用这种方式,E
必须明确指明类型,不能再写 E
了。
如下图,传递 list1、list2、list3
都可以,而 list4
中的形参是 Student2
,跟继承结构没有关系,因此报错。

如果使用 ? super E
,那就表示 method方法
它能接收集合里面参数的类型是 ?
,即任意的,但是这个类型必须是 E
或者 E所有的父类类型
。
例如下面改为 Fu
,那就表示传递过来的集合的泛型必须是 Fu
或者 Fu的父类
。
public static void method(ArrayList<? super Fu> list) {
}
效果如下图,Fu
和 Ye
是可以的,但是其他两个就不行了。

2)应用场景
1、如果我们在定义类、方法、接口的时候,如果类型不确定,就可以定义泛型类、泛型方法、泛型接口。
2、如果类型不确定,但是能知道以后只能传递某个继承体系中的,就可以泛型的通配符
泛型的通配符的关键点:可以限定类型的范围。
三、练习
需求:
定义一个继承结构:
动物
| |
猫 狗
| | | |
波斯猫 狸花猫 泰迪 哈士奇
属性:名字,年龄
行为:吃东西
波斯猫方法体打印:一只叫做XXX的,X岁的波斯猫,正在吃小饼干
狸花猫方法体打印:一只叫做XXX的,X岁的狸花猫,正在吃鱼
泰迪方法体打印:一只叫做XXX的,X岁的泰迪,正在吃骨头,边吃边蹭
哈士奇方法体打印:一只叫做XXX的,X岁的哈士奇,正在吃骨头,边吃边拆家
测试类中定义一个方法用于饲养动物
public static void keepPet(ArrayList<???> list){
//遍历集合,调用动物的eat方法
}
要求1:该方法能养所有品种的猫,但是不能养狗
要求2:该方法能养所有品种的狗,但是不能养猫
要求3:该方法能养所有的动物,但是不能传递其他类型
Animal.java
public abstract class Animal {
private String name;
private int age;
// 无参构造、有参构造、get、set方法
public abstract void eat();
public String toString() {
return "Animal{name = " + name + ", age = " + age + "}";
}
}
四个子类重写 eat()
方法即可,这里就不书写出来了。
要注意的是,Cat类
和 Dog类
在继承抽象类的时候有两种解决方案
1、继承抽象类,重写里面所有的抽象方法
2、本身Cat也是一个抽象的,让Cat的子类再重写重写方法
此时采取第二种处理方案,因为猫的两个子类中eat的方法体还是不一样的。
Test1.java
public class Test1 {
public static void main(String[] args) {
ArrayList<PersianCat> list1 = new ArrayList<>();
ArrayList<LiHuaCat> list2 = new ArrayList<>();
ArrayList<TeddyDog> list3 = new ArrayList<>();
ArrayList<HuskyDog> list4 = new ArrayList<>();
keepPet(list1);
keepPet(list2);
keepPet(list3);
keepPet(list4);
}
// 要求1:该方法能养所有品种的猫,但是不能养狗
public static void keepPet(ArrayList<? extends Cat> list){
//遍历集合,调用动物的eat方法
}
// 要求2:该方法能养所有品种的狗,但是不能养猫
public static void keepPet(ArrayList<? extends Dog> list){
//遍历集合,调用动物的eat方法
}
// 要求3:该方法能养所有的动物,但是不能传递其他类型
public static void keepPet(ArrayList<? extends Animal> list){
//遍历集合,调用动物的eat方法
}
}
四、泛型总结
1、什么是泛型?
泛型是JDK5引入的特性,可以在编译阶段约束操作的数据类型,并进行检查
2、泛型的好处?
- 统一数据类型
- 把运行时期的问题提前到了编译期间,避免了强制类型转换可能出现的异常,因为在编译阶段类型就能确定下来了
3、泛型的细节?
-
泛型中不能写基本数据类型
-
指定泛型的具体类型后,传递数据时,可以传入该类型和它的子类类型
这个就是我们刚才所说的:数据是具有继承性的
-
如果不写泛型,类型默认是
Object
4、哪里定义泛型?
-
如果将泛型写在类的后面,那么这个类叫做
泛型类
在类名后面定义泛型,创建该类对象的时候,确定类型
-
如果将泛型写在方法的后面,那么这个类叫做
泛型方法
在修饰符后面定义方法,调用该方法的时候,确定类型
-
如果将泛型写在接口的后面,那么这个类叫做
泛型接口
在接口名后面定义泛型,有两种用法:① 实现类确定类型;② 实现类延续泛型
5、泛型的继承和通配符
-
泛型不具备继承性,但是数据具备继承性
-
泛型的通配符:
?
,它也表示一切类型? extends E
:表示可以传递E
或者E所有的子类类型
``? super E
:表示可以传递
E或者
E所有的父类类型`
6、使用场景
- 定义类、方法、接口的时候,如果类型不确定,就可以定义泛型
- 如果类型不确定,但是能知道是哪个继承体系中的,可以使用泛型的通配符