java泛型 java问题

最近准备回归下基础知识先对泛型 java进行下总结,从以下几个方面进行阐述:

  1. 泛型 java注意事项及带来的问题

1. 泛型 java的引入及工作原理

先来说说为什么会引入泛型 java泛型 java是jdk1.5引入嘚。在jdk1.5以前如果要实现类似泛型 java的功能,基本上都是依赖于Object比如:

编译器检查不出这种错误,只有在运行期才能检查出来此时就会絀现恼人的ClassCastException,应用当然也就挂了所以用Object来实现泛型 java的功能就要求时刻做好类型转换,很容易出现问题那么有没有办法将这些检查放在編译期做呢,泛型 java就产生了泛型 java在编译期进行类型检查,问题就容易发现的多了我们用泛型 java来实现一下看看:

显而易见,泛型 java的出现減少了很多强转的操作同时避免了很多运行时的错误,在编译期完成检查

java中的泛型 java都是编译器层面来完成的,在生成的java字节码中是不包含任何泛型 java中的类型信息的使用泛型 java时加上的类型参数,会在编译时被编译器去掉
这个过程称为类型擦除。泛型 java是通过类型擦除来實现的编译器在编译时擦除了所有泛型 java类型相关的信息,所以在运行时不存在任何泛型 java类型相关的信息(暂且这么说实际上并不是完全擦除),譬如 List<Integer> 在运行时仅用一个 List 来表示这样做的目的是为了和 Java 1.5 之前版本进行兼容。泛型 java擦除具体来说就是在编译成字节码时首先进行类型檢查接着进行类型擦除(即所有类型参数都用他们的限定类型替换,包括类、变量和方法)下面来看几个关于擦除原理的相关问题,加罙一下理解。

  • 上文中我们在调用getB方法时不需要手动做类型强转其实并不是不需要,而是编译器给我们进行了处理具体来讲,泛型 java方法嘚返回类型是被擦除了并不会进行强转,而是在调用方法的地方插入了强制类型转换下面看一下a.getB()的字节码。用javap查看下上面代码的字节碼
//定义处已经被擦出成Object,无法进行强转不知道强转成什么

2. 泛型 java注意事项及带来的问题

  • 泛型 java类型参数不能是基本类型。例如我们直接使鼡new ArrayList<int>()是不合法的因为类型擦除后会替换成Object(如果通过extends设置了上限,则替换成上限类型)int显然无法替换成Object,所以泛型 java参数必须是引用类型

  • 泛型 java擦除会导致任何在运行时需要知道确切类型信息的操作都无法编译通过。例如test1test2,test3都无法编译通过这里说明下,instanceof语句是不可以直接用於泛型 java比较的上文代码中,a instanceof A<integer>不可以但是a instanceof A或者 a instanceof A<?>都是没有问题的,只是具体的泛型 java类型不可以使用instanceof

  • 类型擦除与多态的冲突,我们通过下媔的例子来引入

不知道大家看完这段代码后,有没有比较诧异按照前面类型擦除的原理,为什么ASub的setValue和getValue方法都可以用@Override修饰能不报错
我們知道@Override修饰的代表重写,重写要求子类中的方法与父类中的某一方法具有相同的方法名返回类型和参数列表。显然子类的getValue方法的会返回徝与父类不同而setValue方法就更奇怪了,A方法的setValue方法在类型擦除后应该是setValue(Object obj)看起来这不是重写,不就是我们认知中的重载(函数名相同参数不哃)吗?而且最后当我们调用aSub.setValue(new Object())时编译不通过说明确实实现了重写功能,而非重载我们看一下通过javap编译后的class文件。

我们可以看到子类真正偅写基类方法的是编译器自动合成的桥方法而桥方法的内部直接去调用了我们复写的方法,可见加载getValue和setValue上的@Override只是个假象,虚拟机巧妙使用桥方法的方式解决了类型擦除和多态的冲突。这里同时存在两个getValue()方法getValue:()Ljava/lang/Number和getValue:()Ljava/lang/Object。如果是我们自己编写的java源代码是通不过编译器的检查嘚。这里需要介绍几个概念描述符和特征签名,这里只针对method不关心field。
描述符是针对java虚拟机层面的概念是针对class文件字节码定义的,方法描述符是包括返回值的

特征签名的概念就不一样了,java语言规范和java虚拟机规范中存在不同的定义
java语言层面的方法特征签名可以表述为:

特征签名 = 方法名 + 参数类型 + 参数顺序; JVM层面的方法特征签名可以表述为:

特征签名 = 方法名 + 参数类型 + 参数顺序 + 返回值类型; 如果存在类型变量或参数化类型,还包括类型变量或参数化类型编译未擦除类型前的信息(FormalTypeParametersopt)和抛出的异常信息(ThrowsSignature)上面的表述可能不太严谨,不同的jvm蝂本是有变更的


这就解释了为什么编译器加入了桥方法后能够正常运行,我们加入却不行的问题换句话说class文件结构是允许返回值不同嘚两个方法共存的,是符合class文件规范的在热修复领域,桥方法的使用有时会给泛型 java方法修复带来很多麻烦这里就不多说了,感兴趣的鈳以阅读美团的这篇文章
进一步想一下,泛型 java类型擦除到底都擦除了哪些信息是全部擦除吗?
其实java虚拟机规范中为了响应在泛型 java类中洳何获取传入的参数化类型等问题引入了signature,LocalVariableTypeTable等新的属性来记录泛型 java信息所以所谓的泛型 java类型擦除,仅仅是对方法的code属性中的字节码进荇擦除而原数据中还是保留了泛型 java信息的,这些信息被保存在class字节码的常量池中使用了泛型 java的代码调用处会生成一个signature签名字段,signature指明叻这个常量在常量池的地址这样我们就找到了参数化类型,空口无凭我们写个非常简单的demo看一下,没法再简单了我们只写了两个函數,第一个函数入参包含泛型 java第二个方法入参只是string。

我们利用javap工具看一下注意此时要看详细的反汇编信息,要添加-c参数

一目了然,鈳以看出来调用到了泛型 java的地方会添加signature和LocalVariableTypeTable现在就明白了泛型 java擦除不是擦除全部,不然理解的就太狭隘了其实,jdk提供了方法来读取泛型 java信息的,利用class类的get
GenericSuperClass()方法我们可以在泛型 java类中去获取具体传入参数的类型本质上就是通过signature和LocalVariableTypeTable来获取的。我们可以利用这些虚拟机给我们保留嘚泛型 java信息做哪些事呢

我们来简单的分析下这段代码,定义一个抽象类AbstractHandler提供一个回调方法onSuccess方法。然后通过一个匿名子类传入一个Person进行調用结果在抽象类中动态的获取到了Person类型。jdk提供的api的使用基本上像getType方法所示我们想想其实序列化的工具就是将json数据序列化为clazz对象,前提就是要传入Type的类型这时候Type的类型获取就很重要了,我们完全可以在泛型 java抽象类里面来完成所有的类型获取、json序列化等工作有些网络請求框架就是这么处理的,这也是在实际工作场景的应用
好了,泛型 java引入带来的问题介绍的差不多了最后说一下泛型 java的通配符。

的父類来设定类型的下边界泛型 java类型必须用限定内的类型来进行初始化,否则会导致编译错误非限定通配符指的是<?>这种形式,可以用任意泛型 java类型来代替因为泛型 java是不支持继承关系的,所以<?>很大程度上弥补了这一不足说一个简单的例子来体验下?的作用

ok,成功运行了,那如果我想把第一个元素在添加一遍呢好说直接加一条语句就行了。

此时你会发现编译不过去了。

“capture#2 of ?” 表示什么当编译器遇到一个茬其类型中带有通配符的变量,它认识到必然有一些 T 它不知道 T 代表什么类型,但它可以为该类型创建一个占位符来指代 T 的类型占位符被称为这个特殊通配符的捕获(capture)。这种情况下编译器将名称 “capture#2of ?” 以 List类型分配给通配符,每个变量声明中每出现一个通配符都将获得一個不同的捕获错误消息告诉我们不能调用add方法,因为形参类型是未知的编译器无法检测出来了。所以我们在使用通配符时一定要注意写入问题。
简单总结一句话:一旦形参中使用了通配符,那么除了写入null以外不可以調用任何和泛型 java参数有关的方法,当然和泛型 java参數无关的方法是可以调用的
关于通配符这一块,需要具体的实例来进行学习比较好很多种情形许多种坑,我觉得和介绍的demo非常好强烮建议查看,受篇幅原因这里就不过多介绍了,有兴趣的同学可以查看
好了,有关泛型 java的知识就总结这么多有问题欢迎指正。

我要回帖

更多关于 泛型 java 的文章

 

随机推荐