泛型的通配符
泛型中的通配符就是?,用?来表示无法确定的泛型类型。
通配符是用来解决泛型无法协变的问题的,协变指的就是如果 Student
是Person
的子类,那么 List<Student>
也应该是 List<Person>
的子类。但是泛型是不支持这样的父子类关系的。
1、泛型 T 是确定的类型,一旦你传了我就定下来了,而通配符则更为灵活或者说是不确定,更多的是用于扩
充参数的范围.
2、或者我们可以这样理解:泛型T就像是个变量,等着你将来传一个具体的类型,而通配符则是一种规定,
规定你能传哪些参数。
通配符分为通配符上界和通配符下界
通配符上界
<? extends 上界>
<? extends Number>//可以传入的实参类型是Number或者Number的子类
对于通配符上界来说,其适合读取数据,而不适合写入数据。 因为对于通配符上界来说,其有一个明确的上界,比如上面的Numbers,那么我们如果对一个泛型集合操作,就可以知道这个泛型集合中存的元素一定都是Number类或者其子类,那么读取元素时就可以统一使用Number类型的变量来进行接收。但是写入就不可以了,因为这个类比如Number可能很多子类,所以添加时候其实这些子类都可以添加进泛型集合,但是编译器无法确定具体的类型(只知道它们是Number类或其子类),为了安全,只允许读取。
通配符下界
<? super 下界>
<? super Integer>//代表 可以传入的实参的类型是Integer或者Integer的父类类型
对于通配符下界来说,其适合写入数据,而不适合读取数据。 因为对于通配符下界来说,其有一个明确的下界,比如上面的Integer,那么我们如果对一个泛型集合操作,就可以知道这个泛型集合中引用的对象一定都是Integer类或者其父类,那么存取元素时只要元素粒度比Integer小的都可以存进去。但是读取就不可以了,因为这个类比如Integer可能很多父类,读取元素时你怎么知道读取的是哪个子类呢,也就是无法用一个准确的类型的变量去接收数据(或者用Object这个公共父类接收)。
总结:
因为读取数据时因为Java的多态机制,所以我们可以使用父类引用接收其子类对象,但是写入数据时编译器需要知道其真正的类型。
深入理解泛型
Type是Java 编程语言中所有类型的公共高级接口(官方解释),也就是Java中所有类型的“爹”;其中,“所有类型”的描述尤为值得关注。它并不是我们平常工作中经常使用的 int、String、List、Map等数据类型,而是从Java语言角度来说,对基本类型、引用类型向上的抽象;
Type体系中类型的包括:原始类型(Class)、参数化类型(ParameterizedTypricArrayType)、类型变量(TypeVariable)、基本类型(Class);
原始类型,不仅仅包含我们平常所指的类,还包括枚举、数组、注解等;
参数化类型,就是我们平常所用到的泛型List、Map<String,Integer>这种;
数组类型,并不是我们工作中所使用的数组String[] 、byte[],而是带有泛型的数组,即T[] ;
基本类型,也就是我们所说的java的基本类型,即int,float,double等
Type体系的出现主要是为了解决泛型的一系列问题。 比如获取泛型的相关信息,泛型所在类的相关信息等。
其中参数化类型和类型变量都是泛型,只不过参数化类型是定义准确的泛型,即定义类时泛型的类型就是确定的,比如List,而类型变量则是不确定的泛型,在定义类时以一个我们自定义的泛型变量名来暂时表示比如T,V等,具体如List然后类中所有用到泛型的地方都使用T来暂时表示。带有泛型的变量,方法参数都是参数化类型(定义时类时使用了泛型这个类的类型就是参数化类型),那么泛型是用一个模糊的变量表示的就是类型变量。比如List,它整体就是一个参数化类型,T是个类型变量。因为类型变量是不确定类型的,所以我们无法获取类型变量的类型(可以获取变量名称T,泛型声明所在类List,或者其上下界),但是可以获取参数化类型的类型。
但是Java自带的这个体系并不那么好,如果我们依赖于整个Type体系去处理泛型代码非常的繁琐,并且不易于理解。基于这种情况,Spring开发了一个ResolvableType
类,这个类对整个Type体系做了系统的封装。
感谢耐心看到这里的同学,觉得文章对您有帮助的话希望同学们不要吝啬您手中的赞,动动您智慧的小手,您的认可就是我创作的动力!
之后还会勤更自己的学习笔记,感兴趣的朋友点点关注哦。