【Java】泛型的继承和通配符

一、泛型不具备继承性,但是数据具备继承性。

基础代码如下,这里已经写好了一个继承结构

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 的时候,他就会报错。

image-20240426191637264

报错的原因就是因为:泛型不具备继承性。

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

image-20240426191851707

但是数据具备继承性。

也就是说我们现在往 list1集合 中可以添加 YeFuZi 的对象,这些添加的 ``YeFuZi 的对象 就是数据。

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,跟继承结构没有关系,因此报错。

image-20240426194110049

如果使用 ? super E,那就表示 method方法 它能接收集合里面参数的类型是 ?,即任意的,但是这个类型必须是 E或者 E所有的父类类型

例如下面改为 Fu,那就表示传递过来的集合的泛型必须是 Fu 或者 Fu的父类

public static void method(ArrayList<? super Fu> list) {
}

效果如下图,FuYe 是可以的,但是其他两个就不行了。

image-20240426194430722

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、使用场景

  • 定义类、方法、接口的时候,如果类型不确定,就可以定义泛型
  • 如果类型不确定,但是能知道是哪个继承体系中的,可以使用泛型的通配符
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值