1 JAVA的Type类型体系

  • 先了解下java的Type类型体系,Type是所有类型(原生类型-Class、参数化类型-Parameterizedtype、数组类型-GenericArrayType、类型变量-TypeVariable、基本类型-Class)的共同接口;前两篇反射和注解讲到的Class<T>就是Type的一实现类

  • Type下面又有四个子接口类ParameterizedType、TypeVariable、GenericArrayType、WildcardType
    • List<E>表示泛型,E是TypeVariable类型,List<String>则是ParameterizedType(参数化类型),List<String>里的String称为实际参数类型
    • 具体化泛型中的类型时,可以使用 ? extends 或 ? super来表示继承关系;如List<\? extends Data\>,而里面的 ? 称为通配符类型
    • GenericArrayType 表示一种元素类型是ParameterizedType(参数化类型)或者TypeVariable(类型变量)的数组类型,如T[] 或者 List[]
  • 注解是JDK1.5才出现了的,为了表示被注解的类型的,加入AnnotatedElement类型,字面意思就是被注解的元素。JDK1.8又有了AnnotatedType将Type和被注解元素的概念关联起来。

  • AnnotatedType也有四个子接口,和Type的四个子接口一一对应,如:ParameterizedType类型被注解则被编译器解析成AnnotatedParameterizedType: @AnTest("list")List<String>list

2 泛型的概念

  • Java 泛型(generics)是JDK1.5中引入的一个新特性,其本质是参数化类型,解决不确定具体对象类型的问题;其所操作的数据类型被指定为一个参数(type parameter)这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法

泛型: 把类型明确的工作推迟到创建对象或调用方法的时候才去明确的特殊的类型

3 泛型类和泛型方法的示例

  • 泛型类的定义
public class MainTest<T> {
private T param;
}
// 存在上界Object
class MainTest<T extends Object> { }
// 存在上界Object
class MainTest<T extends Object> { }
public static void main(String[] args){
MainTest<String> data = new MainTest<String>(){};
ParameterizedType genType1 = (ParameterizedType)data.getClass().getGenericSuperclass();
}
  • 泛型方法的定义
public class MainTest{
public static void main(String[] args){
printData("siting");
}
static <T> T printData(T t){
System.out.println(t);
return t;
}
}
  • 接口和抽象类都可以使用泛型

4 类型擦除

  • 创建泛型的实例时,jvm是会把具体类型擦除的;编译生成的字节码中不包含泛型中的类型参数,即都擦除成了ArrayList,也就是被擦除成"原生类型",这就是泛型擦除
public class MainTest {
public static void main(String[] args){
List<String> strArr = new ArrayList<>();
List<Integer> intArr = new ArrayList<>();
Type strClazz = strArr.getClass();
Type intClazz = intArr.getClass();
}
}

  • 查看编译后的字节码文件是如何表示的: idea菜单 -> view -> show ByteCode
public class MainTest<T> {
T param;
public static void main(String[] args){
MainTest<String> test = new MainTest<>();
test.setParam("siting");
}
public T getParam() { return param; }
public void setParam(T param) { this.param = param; }
}
public class com/MainTest {
...省略
public static main([Ljava/lang/String;)V
L0
LINENUMBER 7 L0
NEW com/MainTest
DUP
INVOKESPECIAL com/MainTest.<init> ()V
ASTORE 1
L1
LINENUMBER 8 L1
ALOAD 1
LDC "siting" // 调用类型擦除后的setParam(Object)
INVOKEVIRTUAL com/MainTest.setParam (Ljava/lang/Object;)V
L2
...省略//getParam 的返回值是Object
public getParam()Ljava/lang/Object;
L0
LINENUMBER 10 L0
ALOAD 0
GETFIELD com/MainTest.param : Ljava/lang/Object;
ARETURN
...省略//setParam 的入参是Object
public setParam(Ljava/lang/Object;)V
L0
LINENUMBER 11 L0
ALOAD 0
ALOAD 1
PUTFIELD com/MainTest.param : Ljava/lang/Object;
RETURN
...
}
  • 可以看出T(String)都被转换为Object类型,最初的初始化的String不见了

5 参数化类型ParameterizedType

public interface ParameterizedType extends Type {
//获取实际参数,List<String>里的String; 如果是List<T>则是TypeVariable类型
Type[] getActualTypeArguments();
// 获取原始类型List<String> -> List<E>
Type getRawType();
Type getOwnerType();
}
  • 需要注意的点,我们不能直接获取指定具体参数的泛型的类型,如Class clazz = List<String>.class编译时不通过的;还有就是直接通过泛型类new创建的对象,其Class并非ParameterizedType类型,而是泛型本身的class,示例如下
public class MainTest<T> {
public static void main(String[] args){
MainTest<String> str = new MainTest<String>();
Class variable = str.getClass();
Type genType1 = variable.getGenericSuperclass();
}
}

  • 被具体参数化的泛型才能被编译器识别为ParameterizedType类型,有三种方式获取ParameterizedType类型
// 1 子类继承泛型时,指定具体参数(可以是String等已知类型,也可以是子类的泛型参数)
// 2 获取在类内部定义的泛型属性
// 3 局部代码,可以通过泛型的匿名内部子类获取ParameterizedType类型
public class MainTest<T> {
List<T> list;
public static void main(String[] args) throws NoSuchFieldException {
SubTest<String> str = new SubTest<>();
// 方式一
Class variable = str.getClass();
// 父类是(521)ParameterizedType类型
ParameterizedType genType = (ParameterizedType)variable.getGenericSuperclass();
// (521)ParameterizedType类型的原生类型是(479)class com.MainTest
Type clazz = genType.getRawType();
//MainTest.class 的原生类型是 (479)class com.MainTest
Class rawClazz = MainTest.class; //方式二,泛型属性
Field field = rawClazz.getDeclaredField("list");
//属性list 类型是(546)ParameterizedType类型List<T>
ParameterizedType fieldType = (ParameterizedType)field.getGenericType(); // 方式三
MainTest<String> sub3 = new MainTest<String>(){};
// clazz3是匿名子类
Class clazz3 = sub3.getClass();
//父类是(555)ParameterizedType类型
ParameterizedType genType3 = (ParameterizedType) clazz3.getGenericSuperclass();
// (555)ParameterizedType类型的原生类型是(479)class com.MainTest
Type type3 = genType3.getRawType();
}
public static class SubTest<R> extends MainTest<R>{ }
}

6 泛型的继承

  • 子类可以指定父类的泛型参数,可以是已知类(Integer、String等),也可以用子类自己的泛型参数指定
  • 泛型被继承时,且指定父类泛型参数,则额外生成的ParameterizedType类型作为子类的父类;如果没有指定父类泛型参数,则直接继承原生类型
public class MainTest<T> {
T param;
static public class SubTest1 extends MainTest<String>{}
static public class SubTest2<R> extends MainTest<R>{}
//SubTest3继承的时原生类型
static public class SubTest3 extends MainTest{}
}

7 泛型变量TypeVariable

  • (先临时定义一个名称,Test<E>里的E为泛型参数);泛型变量TypeVariable:泛型的泛型参数就是TypeVariable;当父类使用子类的泛型参数指定自身的泛型参数时;或者泛型属性定义在泛型类A<T>中,并使用泛型类A<T>的泛型参数T时,其泛型参数都会被编译器定为泛型变量TypeVariable,而不是被擦除
public class MainTest<T> {
List<T> param;
public static void main(String[] args) throws Exception{
Class clazz = MainTest.class;
TypeVariable[] typeVariable = clazz.getTypeParameters();
// 1
Field field = clazz.getDeclaredField("param");
ParameterizedType arrayType = (ParameterizedType)field.getGenericType();
// interface List<E> 的泛型类型E被T,具体化,因此其被识别为 TypeVariable
TypeVariable variable1 = (TypeVariable)arrayType.getActualTypeArguments()[0];
// 2
ParameterizedType type = (ParameterizedType)SubTest.class.getGenericSuperclass();
TypeVariable variable2 = (TypeVariable)type.getActualTypeArguments()[0];
}
static class SubTest<R> extends MainTest<R>{}
}

8 通配符(WildcardType)

无边界通配符:无界通配符 ? 可以适配任何引用类型:

  • 当方法参数需要传入一个泛型时,而且无法确定其类型时。直接使用无具体泛型变量的泛型,容易造成安全隐患;若在方法代码里进行类型转换,极容易出现ClassCastException错误
  • 那泛型变量用Object代替不就行了?但是泛型类+的类型具体参数转变的ParameterizedType(参数化类型)是不存在继承关系,是不同的类型;即Object是String的父类,但是List<Object>和List<String>的类型是不同的两个ParameterizedType,不存在继承关系。于是有了类型通配符 ?
public static void print(List list){}
----->>>
public static void print(List<?> list){}

  • 无界通配符可以匹配任意类型;但是在使用?时,不能给泛型类的变量设置值,因为我们不知道具体类型是什么;如果强行设置新值,后面的读容易出现ClassCastException错误。因此编译器限制了通配符 ?的泛型只能读不能写

上界限定通配符 < ? extends E>

  • 想接收一个List集合,它只能操作数字类型的元素【Float、Integer、Double、Byte等数字类型都行】,怎么做?可以使用List<? extends Number的子类>,表明List里的元素都是Number的子类
    public static void print(List<? extends Number> list) {
Number n = new Double("1.0");
list.add(n);
Number tmp = list.get(0);
}

  • 图片里可以看出,存在上界通配符,因为具体类型不确定,也是只能读不能写的

下界限定通配符 < ? super E>

class Parent{ }
class Child extends Parent{ }
public class MainTest<T> {
T param;
public static void main(String[] args){
MainTest<? super Child> parent_m = new MainTest<>();
parent_m.setParam(new Child());
Object parent = parent_m.getParam();
}
public T getParam() { return param; }
public void setParam(T param) { this.param = param; }
}

  • 如果定义了通配符是谁的父类,则是下界限定通配符;此类通配符可读可写,转成任意父类都不会出现ClassCastException错误。
  • 个人猜想:难道是因为通配符上界限定通配符的泛型 向下转型容易出现ClassCastException错误,而下界限定通配符向上转型不会出现ClassCastException错误,因此java规范限制前者编译出错,而后面编译通过?

9 泛型数组(GenericArrayType)

public interface GenericArrayType extends Type {
//获得这个数组元素类型,即获得:A<T>(A<T>[])或T(T[])
Type getGenericComponentType();
}
  •  GenericArrayType,泛型数组,描述的是ParameterizedType类型以及TypeVariable类型数组,即形如:Test<T\>[][]、T[]等,是GenericArrayType的子接口
public class MainTest<T> {
T[] param;
public static void main(String[] args) throws Exception{
Class clazz = MainTest.class;
Field field = clazz.getDeclaredField("param");
GenericArrayType arrayType = (GenericArrayType)field.getGenericType();
TypeVariable variable = (TypeVariable) arrayType.getGenericComponentType();
}
}


欢迎指正文中错误

关注公众号,一起交流

基础篇:深入解析JAVA泛型和Type类型体系的更多相关文章

  1. Java泛型学习笔记--Java泛型和C#泛型比较学习(一)

    总结Java的泛型前,先简单的介绍下C#的泛型,通过对比,比较学习Java泛型的目的和设计意图.C#泛型是C#语言2.0和通用语言运行时(CLR)同时支持的一个特性(这一点是导致C#泛型和Java泛型 ...

  2. 泛型和 Any 类型

    泛型和 Any 类型 这两个类型看起来很相似,但是一定要小心两者的区别.他们区别在于 Any 类型会避开类型的检查,所以尽量少用最好不用.泛型一方面很灵活一方面也很安全,下面举个例子感受下两者的区别: ...

  3. Java 基本类型和对象类型的区别

    Java 基本类型和对象类型的区别 基本类型: int long byte float double char boolean short 对象类型: Integer Long Byte Float ...

  4. Java中的集合类型体系(一)

    Java中的集合类型体系(一) 提问:为什么需要集合? 通常情况下,程序需要根据运行时才知道创建了多少对象.若非程序运行时,而在开发阶段,我们并不知道创建了多少对象,甚至不知道对象的准确类型,为了满足 ...

  5. 撸基础篇系列,JAVA的NIO部分

    前言:撸基础篇系列,避免每次都要从头开始看,写个自己的知识体系树 NIO 核心就是异步, 比如,复制文件,让操作系统去处理,等通知 BIO核心类 一,BIO NIO基本操作类 Bytebuffer 构 ...

  6. java 泛型和object比较

    引言 我们使用object和泛型做形参,都是为了让这个方法能接收更多类型的对象,让程序变得更健壮,代码复用率更高.当我们回看自己写的代码时会发现,好像使用泛型的地方使用object也可以,使用obje ...

  7. 【Java心得总结一】Java基本类型和包装类型解析

    说到数据类型这个问题是一个基本的不能再基本的问题,我们当初编程入门第一课一般就是讲数据类型,而今天我想记录的是一个在Java中容易忽略的问题,即基本类型和包装类型. 一.基本类型出现的原因 我们都知道 ...

  8. MyBatis源码解析(十)——Type类型模块之类型处理器TypeHandler

    原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6715063.html 1.回顾 之前的两篇分别解析了类型别名注册器和类型处理器注册器,此二 ...

  9. MyBatis源码解析(八)——Type类型模块之TypeAliasRegistry(类型别名注册器)

    原创作品,可以转载,但是请标注出处地址:http://www.cnblogs.com/V1haoge/p/6705769.html 1.回顾 前面几篇讲了数据源模块,这和之前的事务模块都是enviro ...

随机推荐

  1. [ASP.NET Core开发实战]基础篇04 主机

    主机定义 主机是封闭应用资源的对象. 设置主机 主机通常由 Program 类中的代码配置.生成和运行. HTTP项目(ASP.NET Core项目)创建泛型主机: public class Prog ...

  2. mq本机上能用,其他机子上连上不mq

    有一次遇到了记录下.原因是suse11重启后会自动开防火墙. 关了防火墙. rcSuSEfirewall2 stopShutting down the Firewall done

  3. 和同事谈谈Flood Fill 算法

    前言 今天忙完了公司的工作后,发现同事在做LeeCode的算法题,顿时来了兴趣,于是王子与同事一起探讨如何能做好算法题,今天在此文章中和大家分享一下. 什么是Flood Fill 算法 我们今天谈论的 ...

  4. 深入了解Kafka【四】消费者的Offset管理

    1.Offset Topic Consumer通过提交Offset来记录当前消费的最后位置,以便于消费者发生崩溃或者有新的消费者加入消费者组,而引发的分区再均衡操作,每个消费者可能会分到不同的分区.我 ...

  5. ajax发送请求的时候url为空或者undefined会发生什么

    $.ajax()里的url为空,ajax请求发送到当前自己的页面. 例如index.html里$.ajax()的url为空就发送到index.html

  6. [业界方案] 用SOFATracer学习分布式追踪系统Opentracing

    [业界方案] 用SOFATracer学习分布式追踪系统Opentracing 目录 [业界方案] 用SOFATracer学习分布式追踪系统Opentracing 0x00 摘要 0x01 缘由 &am ...

  7. Java中nextInt和nextLine同时使用出现的问题

    代码: package com.ins1; import java.util.*; public class test { public static void main(String[] args) ...

  8. SqlAnalyzer1.01 源码

    源码下载:https://files.cnblogs.com/files/heyang78/SqlAnalyzer-20200529-2.rar 现有功能:不带函数允许嵌套的select ...fro ...

  9. 深入理解 JVM 的内存区域

    深入理解运行时数据区 代码示例: 1. JVM 向操作系统申请内存: JVM 第一步就是通过配置参数或者默认配置参数向操作系统申请内存空间,根据内存大小找到具体的内存分配表,然后把内存段的起始地址和终 ...

  10. Ubuntu 16.04 安装Python 3.6

    1.配置软件仓库,因为python 3.6 新版没有发布到ubuntu的正式仓库中,咱们通过第3方仓库来做.在命令行中输入: sudo add-apt-repository ppa:jonathonf ...