你是否真正理解了泛型、通配符、类型擦除


你是否还不知道为什么要引入泛型?
你是否还不知道怎么使用泛型?
你是否还不知道类型擦除?
你是否还不知道怎么使用通配符?

那么读上几分钟,让你的人生不再遗憾?哈撒给😁


前言

泛型是JDK1.5引入的新特性,泛型提供了编译时类型检测安全机制。

可能不是太好理解,下面使用代码演示

在这里插入图片描述
没错,当你定义了泛型为Integer类型之后,试图向集合中加入String类型,那编译肯定不会通过。

至于怎么向Integer集合类型中加入String类型,那么就肯定要绕过编译了!
然后你就可以想到使用什么了,没错,就是反射!


一、使用反射绕过编译时的类型检查

        List<Integer> list = new ArrayList<>();
        list.add(2);
        String str = "a";
//        list.add(str);
		//实例化对象
        Class<? extends List> clazz = list.getClass();
		//获取add类,因为反射会将类的属性、构造函数、方法等成为一个个的对象
        Method add = clazz.getDeclaredMethod("add", Object.class);
        //执行invoke方法,向集合中添加数据
        add.invoke(list,str);
        System.out.println(list);

在这里插入图片描述
至于原理我已经在反射一文讲清楚了。该文是讲泛型的,反射就不多bb了。


二、为什么要引入泛型的呢?

在JDK1.5之前,如果想要实现一个通用的方法,那么就需要使用Object

/**
 * @author:抱着鱼睡觉的喵喵
 * @date:2021/4/1
 * @description:
 */
public class Common {

    private Object[] data;

	public Common(int length) {
		this.data = new Object[length];
	}
    public Object getData(int index) {
        return data[index];
    }

    public void addData(int index, Object data) {
        this.data[index] = data;
    }
}

然后就使用呗

        Common common = new Common(5);
        common.addData(0,"zsh");
        common.addData(1,23);
        String data = (String) common.getData(0);
        String data2 = (String) common.getData(1);
        System.out.println(data);
        System.out.println(data2);

在这里插入图片描述
从这可以看出使用Object可以实现方法的通用,但是有个很大的弊端

当我向数组中加入整型和字符串时,在获取数据时,转换为String类型失败,当然都能明白这个应该转换为Integer类型

但是如果数据量较大时,是不是很难避免类型转换异常

所以为了代码得到重(chong)用等等一系列的原因,泛型便出现了


三、泛型的使用方式

1.泛型类

public class Generic<T> {
    private T data;

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}
public class GenericExtends<T> extends Generic<T> {
    
}
class GenericExtends2<T> extends Generic<String> {
    
}

2.泛型方法

public class Generic<T> {

    public <E> void iterator(E[] arr) {
        for (E ele : arr) {
            System.out.printf("%s\t",ele);
        }
    }
}
 Generic generic = new Generic();
        Integer arr[] = {1,35,56};
        String[] str ={"hello"};
        generic.iterator(arr);
        generic.iterator(str);

在这里插入图片描述

3.泛型接口

public interface Alorgithm<T>{
    T method();
}

四、通配符的使用

我们经常会遇见这几种通配符,如T E K V ?,那么它们都是什么意思呢?

其实这几个字符是约定俗成的,你可以使用A-Z的任何一个字符,但这样的话,不太容易理解语义。

T:表示一个Java类型
?: 表示不确定的 java 类型
K V : java键值中的Key Value
E :代表Element

或许不是很好理解,那么看完下面的例子你就明白了

1.通配符上限<? extends T>

现在我自定义四个类,分别是Car、BusCar、SportCar、Airport
其中BusCar和SportCar继承自Car,Airport与其它几个类无任何关系

public class Car {

    public String toString() {
        return "car ---> run build";
    }
}

public class BusCar extends Car{

    @Override
    public String toString() {
        return "busCar ----> run build";
    }
}

public class SportCar extends Car{

    @Override
    public String toString() {
        return "sportCar -----> run build";
    }
}
public class Airport {
    public String toString() {
        return "airPort ---> run build";
    }
}

现在我要对Car类型的子类型车做出统一操作

  public static void start(ArrayList<Car> cars) {
        for (Object car : cars) {
            System.out.println(car);
        }
    }

    public static void main(String[] args) {
        ArrayList<BusCar> list = new ArrayList<>();
        BusCar busCar = new BusCar();
        BusCar busCar2 = new BusCar();
        list.add(busCar);
        list.add(busCar2);
        start(list);

    }

在这里插入图片描述
发现ArrayList<BusCar>向ArrayList< Car>类型转换失败了,那这是为什么呢? 我们都知道BusCar是直接可以转换为Car类的;子类->父类

其实这也很简单,ArrayList<BusCar>和ArrayList< Car>是不同的类型,我们都知道泛型都有安全类型检测;假如说上面的可以转换,那岂不是我也可以向其中添加SportsCar了,甚至Car本身都能添加,这样就乱套了。(本来是一群BusCar,突然来了一个SportsCar,这个SportsCar还不被群殴死呀)


重点来了

所以可以使用?
在这里插入图片描述
再次运行
在这里插入图片描述
这样以来也可以保证类型的安全,如果在这种情况下加入其他类型的就会报错。
在这里插入图片描述

虽然可以这样使用,但是如果我直接向其中只加入Airport类会怎么样呢(Airport不是Car的子类)
在这里插入图片描述
这样以来是不是有很大的问题,本来start方法只是用来只执行Car类或者Car子类的;突然跑来一个无关的Airport(飞机)类,而且还执行成功了,是不是很荒唐,所以通配符上限就来了。

<? extends T> 表名该类型必须是T类或者T的子类,T就代表向上的极限值。
在这里插入图片描述
如果再使用与Car无关的类,就会报错;
在这里插入图片描述


2.下界通配符 < ? super E>

表示该类型必须是E本身或者E的父类(超类),上限是Object
在这里插入图片描述
在这里插入图片描述

3. T 和 ? 的区别


1.有时候我们可以对 T 进行操作,但是对 ?却 不行,比如如下这种

T car = start();	//正确

? car = start();	//错误

T表示一个Java类型
?表示一个不确定的Java类型

2.T可以多重限定而?不行

这个T必须是Auth和Alorgithm的共同子类;?不可以的原因是?表示的是一个不确定的类型;

public class Alu implements Alorgithm,Auth{
//这个T必须是Auth和Alorgithm的共同子类
    private static<T extends Auth & Alorgithm> void test(T t) {

    }
//接口方法
    @Override
    public Object method() {
        return null;
    }
}

3.通配符?可以使用超类限定而T不可以

T extends A

? extends A
? super A


五、类型擦除

  List<Integer> list = new ArrayList<>();
        list.add(2);
        String str = "a";
        list.add(str);		//报错
        System.out.println(list);

这样添加一个整型之后,再添加一个字符串,肯定报错;因为泛型在编译期间会进行类型安全检测

但是下面这样就可以

  List<Integer> list = new ArrayList<>();
        list.add(2);
        String str = "a";
		//实例化对象
        Class<? extends List> clazz = list.getClass();
		//获取add对象,因为反射会把属性,构造函数,方法等分为一个个的对象
        Method add = clazz.getDeclaredMethod("add", Object.class);
        //执行invoke操作,也就是添加str到list中
        add.invoke(list,str);
        System.out.println(list);

使用反射绕过了编译,其实Java的泛型是伪泛型,意思是在编译期间,所有的泛型信息都会被擦除;比如上面我定义的泛型是List< Integer >类型,但是到了编译过后就变成了List,JVM只看到了List,看不到任何泛型的信息;

所以在编译器要尽可能的发现错误;但仍无法避免类型转换异常

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Thecoastlines

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值