一文搞懂java中的泛型

一、泛型的概述

1.1 泛型是什么?为什么要使用泛型?

  在Java 1.5发行版本中增加了泛型。没有泛型之前,向元生态集合中插入任何类型的元素,取出数据后又必须将从集合中读取到的每一个对象进行转换。如果不小心插入了类型错误的对象,在运行时的转换处理就会出错。有了泛型之后,可以告诉编译器每个集合中接受哪些对象类型。编译器自动为你的插入进行转换,并在编译时告知是否插入了错误类型的对象。这样是程序更加安全和更加清楚。

比如说下面这段代码

        ArrayList arrayList = new ArrayList();
        arrayList.add("123");
        arrayList.add(123);

        for (int i=0;i<arrayList.size();i++) {
            System.out.println((String) arrayList.get(i));
        }

在编译期间并不会有什么问题,但是只要运行就会报错

但是如果将上面的代码改为

        ArrayList arrayList = new ArrayList<String>();
        arrayList.add("123");
        arrayList.add(123);

        for (int i=0;i<arrayList.size();i++) {
            System.out.println((String) arrayList.get(i));
        }

 这段代码则在编译的时候就会报错,这种提前预知错误可以避免在一些事故的发生。

  泛型又可以分为:泛型方法、泛型接口、泛型类

二、泛型的定义

2.1 泛型类

定义一个泛型类的方法

class 类名<泛型标识> {
  private 泛型标识  成员变量;
}

泛型标识可以是

  • T:代表一般的任何类型
  • E:代表Element 元素或者Exception异常的意思
  • K:代表key的意思
  • V:代表value的意思
  • S:代表subtype的意思

也可以是任意自己定义的字母符号

值得注意的是:类上定义的泛型不能够使用在静态变量和静态方法上,这是因为泛型类中的类型参数的确定是发生在创建泛型对象的时候,而静态变量和静态方法是在类编译的时候就需要确定,并且可通过类名进行调用,在类型并没有确定是静态变量也可以被调用,因此泛型类的类型参数是不能在静态成员中使用的。但是静态方法是可以定义自己的泛型的,方法如下

public class TestOne<T> {

    private T key1;

    public static <E, U> U method01(E e, U u) {
        return u;
    }

    public static void main(String[] args) {
        System.out.println(TestOne.method01(1, 1.1));
    }

2.2 泛型接口

定义一个泛型接口的方法为

class interface 接口名<类型参数> {
}

泛型接口的泛型的类型确定是在泛型接口被实现的时候

2.3 泛型方法

  泛型方法中的泛型需要在返回值之前进行声明,没有进行声明的泛型是不能够使用的,如果使用了类上声明的泛型则不属于泛型方法

定义一个泛型方法

public <类型参数> 返回类型 方法名(类型参数 变量名) {
    ...
}

泛型方法中声明的泛型只能在该方法中使用,同时泛型方法能够使用类上面定义的泛型。泛型方法中的泛型的确定是在方法被调用的时候,当调用泛型方法的时候,根据外部传入的实际对象的类型,编译器就可以判断出类型参数所代表的具体数据类型,所以静态方法也可以定义为泛型方法。

  当泛型方法的形参列表中有多个类型参数时,在不指定类型参数的情况下,方法中申明的类型参数为泛型方法中的几种类型参数的共同父类最小集,直到Object。

三、泛型擦除

3.1 什么是泛型擦除

  泛型擦除就是指泛型的信息只会存在于代码的编译阶段,当代码编译结束之后,与泛型相关的信息就会被擦除,也就是说编译之后的class文件中是没有泛型信息的,运行的时候也不会有泛型信息。

ArrayList<String> strings = new ArrayList<>();
ArrayList<Integer> integers = new ArrayList<>();
System.out.println(strings.getClass()==integers.getClass());

如上代码输出的结果为true。就是由于泛型擦除,在代码编译完成之后已经没有泛型信息了,泛型信息被擦除之后生成的Class对象都是ArrayList<Object>。所以在泛型擦除之后,所有被指定的无限定的泛型变量类型都会被擦除而成为原始类型 。那什么是无限定的泛型变量和有限定的泛型变量呢?T、E、K、V等属于无限定的泛型变量,而<T extends XXX>或者<XXX super T>则属于有限定的泛型变量。看下面的代码

 

  既然在编译的时候泛型被擦除了,类型变成了原始类型,ArrayList<String>编译之后应该成为了ArrayList<Object>,那为什么调用add方法添加Integer类型的数值之后还会报错呢。

  因为编译器会将类型信息记录,编译之前会进行类型检查,发现添加的元素类型并不是泛型类型,所以会报错。如果检查通过,则在获取元素的时候又会将原始类型自动转为类型擦除前的类型。

四、泛型通配符

4.1 泛型之间的继承关系

  我们在创建泛型对象的时候是可以这么使用的

List<String> strList = new ArrayList<>();

这表示向上转型,只需要List和ArrayList中的泛型类型是一样即可。但是,如果如下这么写,则会报错

ArrayList<Object> strings = new ArrayList<String>();

所以说泛型类中即使<>中存在继承关系,也是不能相互赋值的。

ArrayList<Integer> intList = new ArrayList<>();
intList.add(1);
ArrayList<Number> numList = intList;
numList.add(1.1);

Integer result = intList.get(1);

假设第3行代码不会报错,则第五行代码再获取第二个元素的时候是1.1,将其赋值给Integer类型则会报类型转换错误。

4.2 泛型通配符

既然泛型类<>中的类型存在父子关系,创建的类对象也是不存在父子关系的,那么有下面这种情形。

 这种情况只有为List<Number>才能传入method方法,其实传入List<Integer>逻辑是一致的,由于List<Number>和List<Integer>不存在父子集成关系所以并不能传入。这种情况如何解决呢,于是引入了泛型通配符。

  泛型通配符有三种格式

  • <?>:无限定的通配符
  • <? extends T>:有上界的通配符
  • <? super T>:有下界的通配符

4.2.1 有上界的通配符<? extends T>

<? extends T>表示父类为T的所有子类,但是不能确定具体是那个类,只能确定是T的子类。所以ArrayList<? extends Number>表示不确定的类型,可以是ArrayList<Integer>、ArrayList<Double>、ArrayList<Float>等,所以下面的代码会报错

ArrayList<? extends Number> numList = new ArrayList<>();
numList.add(new Integer(1));
numList.add(new Double(1.0));

Number number = numList.get(0);

代码的第二行和第三行会报错,因为ArrayList<? extends T>并不能确定具体的类型,所以向其中加入具体类型的元素会报错。但是第四行却不会报错,因为已经确定元素都是Number的子类,所以使用Number类型去获取元素一定没问题。

4.2.2 有下届的通配符<? super T>

经过上面的描述和实验,可以猜测,有下届的统配符表示元素为T的父类,所以添加进去的元素最类型T或者T的父类,同时也不能确定具体的类型。虽然不能确定具体的类型,但是能确定的是最少可以添加T类型的元素,但是由于读的时候不确定是什么类型,所以有下届通配符的类只支持存不支持读,和有上界的通配符正好相反。

4.2.3 无限定的泛型通配符

无限定的泛型通配符<?>表示任何不确定的类型,而任何不确定的类型只有null,所以无限定的通配符只能添加null元素。由于泛型擦除,所以读的时候会转为Object类型。

五、<T>、<Object>、<?>有什么区别

<T>表示一种类型,可以是任何具体的类型,创建对象的时候需要确定类型,添加或者读取的时候元素类型都必须为T;<Object>表示元素类型为Object的类型,可以添加Object类型的元素,读取元素也会转为Object类型;<?>表示不确定的任何类型,所以不能向里面添加任何类型的元素。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值