Java泛型Generics
泛型基础知识
- 泛型:是JDK5中引入的特性,可以在编译阶段约束操作的数据类型,并进行检查。
- 泛型的格式:<数据类型>
- 注意:泛型只能支持引用数据类型。如果我们没有给集合指定类型,默认认为所有的数据类型都是Object类型,此时可以往集合中添加任意的数据类型。带来一个坏处是由于多态的存在,我们在获取数据的时候,无法使用他的特有行为。此时推出了泛型,可以在添加数据的时候就把类型进行统一。而且我们在获取数据的时候,也避免了强转,使用上非常的方便。
泛型的优点
●统一数据类型。
●把运行时期的问题提前到了编译期间,避免了强制类型转换可能出现的异常,因为在编译阶段类型就能确定下来。
Java中的泛型是伪泛型,也即仅存在Java源码中,在编译后的.class文件中泛型信息将不复存在,这种行为称为泛型的擦除。
泛型的注意点 - 泛型中不能写基本数据类型
- 指定泛型的具体类型后,传递数据时,可以传入该类类型或者其子类类型
- 如果不写泛型,类型默认是Object
自定义泛型
泛型可以在很多地方进行定义。
- 类后面 泛型类
- 方法上面 泛型方法
- 接口后面 泛型接口
泛型类
使用场景:当一个类中,某个变量的数据类型不确定时,就可以定义带有泛型的类
格式:
修饰符class类名<类型>{
}
举例:
public class ArrayList<E>{
}
创建该类对象时,E就确定类型。此处E可以理解为变量,但是不是用来记录数据的,而是记录数据的类型,一般可以写成:T、E、K、V等。
泛型方法
方法中形参类型不确定时,可以使用类名后面定义的泛型<E>。
方法中形参类型不确定时
方案①:使用类名后面定义的泛型 所有方法都能用
方案②:在方法申明上定义自己的泛型 只有本方法能用
格式:
修饰符<类型> 返回值类型 方法名(类型变量名){
}
举例:
Public <T> void show(T t){
}
此处T可以理解为变量,但是不是用来记录数据的,而是记录类型的,可以写成:T、E、K、V等。
如下代码中包含一个addAll方法,注意泛型占位符要放在修饰符的后面,本例中是static关键字后面。
import java.util.ArrayList;
public class ListUtil {
private ListUtil(){}
public static<E> void addAll(ArrayList<E> list, E e1, E e2, E e3, E e4, E e5){
list.add(e1);
list.add(e2);
list.add(e3);
list.add(e4);
list.add(e5);
}
}
泛型接口
格式:
修饰符interface接口名<类型>{
}
举例:
public interface List<E>{
}
泛型接口的重点是如何使用一个泛型接口。
- 方式1:实现类给出具体类型
- 方式2:实现类延续泛型,创建对象时再确定
如下:
class MyArrayList2 implements List<String>{
}
class MyArrayList2<E> implements List<E>{
}
泛型的通配符
泛型不具备继承性,但是数据具备继承性。如下代码method方法要求一个ArrayList<Object>类型的参数,而如果传入子类Date(Object是所有类型的父类)的对象列表则会报错。
import java.util.ArrayList;
import java.util.Date;
public class GenericsDemo {
public static void main(String[] args) {
ArrayList<Object> list = new ArrayList<>();
ArrayList<Date> dlist = new ArrayList<>();
method(dlist); // 报错。泛型不具备继承性,但是数据具备继承性。
// 如果要让method方法不报错则应使用list包含Date对象
// 则可以先向list对象中添加Date对象
list.add(new Date());
list.add(new Date());
method(list); // 不会报错。体现出数据具备继承性
}
public static void method(ArrayList<Object> l){
}
}
有时候类或方法虽然不确定类型,但是希望只能传递具有继承关系的一类类型时,我们就可以使用泛型的通配符:
?也表示不确定的类型,他也可以进行类型的限定
- ?: 与字符占位符一样,单独用表示所有类型
- ? extends E:表示可以传递E或者E所有的子类类型
- ? super E:表示可以传递E或者E所有的父类类型
应用场景
1.如果我们在定义类、方法、接口的时候,如果类型不确定,就可以定义泛型类、泛型方法、泛型接口。
2.如果类型不确定,但是能知道以后只能传递某个继承体系中的,就可以泛型的通配符泛型的通配符。
关键点:可以限定类型的范围。
import java.util.ArrayList;
public class GenericsDemo2 {
public static void main(String[] args) {
}
public static void method1(ArrayList<?> p){ // 直接使用?时不用在修饰符后的尖括号类声明?问号
}
public static void method2(ArrayList<? extends Ye> p){
}
public static void method3(ArrayList<? super Fu> p){
}
}
// 爷爷类
class Ye{}
// 父亲类
class Fu extends Ye{}
// 儿子类
class Zi extends Fu{}
综合练习
需求:
定义一个继承结构:
属性:名字,年龄
行为:吃东西
波斯猫方法体打印:一只叫做XXX的,X岁的波斯猫,正在吃小饼干
狸花猫方法体打印:一只叫做XXX的,X岁的狸花猫,正在吃鱼
泰迪方法体打印:一只叫做XXX的,X岁的泰迪,正在吃骨头,边吃边蹭
哈士奇方法体打印:一只叫做XXX的,X岁的哈士奇,正在吃骨头,边吃边拆家
测试类中定义一个方法用于饲养动物
public static void keepPet(ArrayList<???>list){
//遍历集合,调用动物的eat方法
}
要求1:该方法能养所有品种的猫,但是不能养狗
要求2:该方法能养所有品种的狗,但是不能养猫
要求3:该方法能养所有的动物,但是不能传递其他类型
import java.util.ArrayList;
import java.util.Iterator;
public class TestMain {
public static void main(String[] args) {
Cat c1 = new LihuaCat();
}
// 需求1
public static void keepPet1(ArrayList<? extends Cat> cats){
Iterator<? extends Cat> citer = cats.iterator();
while (citer.hasNext()){
citer.next().eat();
}
}
// 需求2
public static void keepPet2(ArrayList<? extends Dog> dogs){
Iterator<? extends Dog> citer = dogs.iterator();
while (citer.hasNext()){
citer.next().eat();
}
}
// 需求3
public static void keepPet3(ArrayList<? extends Animal> animals){
Iterator<? extends Animal> citer = animals.iterator();
while (citer.hasNext()){
citer.next().eat();
}
}
}
泛型小结
1.什么是泛型?
JDK5引入的特性,可以在编译阶段约束操作的数据类型,并进行检查
2.泛型的好处?
●统一数据类型
●把运行时期的问题提前到了编译期间,避免了强制类型转换可能出现的异常,因为在编译阶段类型就能确定下来。
3.泛型的细节?
●泛型中不能写基本数据类型
●指定泛型的具体类型后,传递数据时,可以传入该类型和他的子类类型
●如果不写泛型,类型默认是Object
4.哪里定义泛型?
●泛型类:在类名后面定义泛型,创建该类对象的时候,确定类型
●泛型方法:在修饰符后面定义方法,调用该方法的时候,确定类型
●泛型接口:在接口名后面定义泛型,实现类确定类型,实现类延续泛型
5.泛型的继承和通配符
●泛型不具备继承性,但是数据具备继承性
●泛型的通配符:?
? extend E
? super E
6.使用场景
●定义类、方法、接口的时候,如果类型不确定,就可以定义泛型
●如果类型不确定,但是能知道是哪个继承体系中的,可以使用泛型的通配符