java泛型中的各种限制
java和其他语言一样,都支持泛型,包括泛型类和泛型方法,但是java的泛型比较特殊。因为java的泛型并不是在java诞生之初就加入的,在很长的一段时间里,java是没有泛型的,在需要泛型的地方,统统都采用协变的方式,也就是采用Object,比如ArrayList类,元素的类型就是Object。为了兼容原来的代码,java的设计者希望在加入泛型之后,所有的泛型都可以传给原来的对应的非泛型参数,例如,可以把ArrayList<xxx>传给原来接收ArrayList参数的方法。为了达到这个目的,java的设计者采取了一种叫做“类型擦除”的实现方式,把泛型拦截在了编译阶段,在虚拟机中,所有的泛型参数最终的类型都会是转换后的Object类型(如果有类型限定,则是限定后的类型,如 <T extends Super>,则泛型T在虚拟机中统一都是Super类型),然后利用协变性而兼容所有类型或限定类型参数的传入,从而实现泛型。
比如用户定义泛型类:
class Super<T,V>{
T a;
public V mehod(T arv){
a=arv;
return a;
}
}
在经过编译阶段的类型擦除之后,虚拟机看到的Super类是这样的:
class Super{
Object a;
public Object mehod(Object arv){
a=arv;
return a;
}
}
而且可以看到,无论有多少个实例化后的泛型,比如Super<String,String>、Super<Interger,Double>,在虚拟机中,都只存在一个Super类。
在应用泛型类的时候,如果返回值也是泛型,那么返回的将是Object,但是很显然,我们在使用泛型的过程中,并没有要显示的进行类型转换,比如我们不需要这样 String a=(String) super.method("hahaha"); 而是直接 String a= super.method("hahaha"); 就可以了。为什么呢?
则是因为,为了返回实现类型的自动匹配,java编译器会在“类型擦除”的基础上在进行“转换插入”操作。每当使用泛型的返回值时,编译器会制动插入类型转换,所以原始代码是 String a= super.method("hahaha"); ,编译之后,虚拟机看到的却是 String a= (String)super.method("hahaha"); ,类型转换被“插入”了。
因为类型擦除后,原有的泛型方法的泛型参数都被替换成了Object,因此无法实现方法覆盖,但实际应用中,肯定需要实现方法覆盖从而达到多态的目的。
比如有用户继承了Super<T,V>这个泛型,实现了子类SubClass:Super<String,String>:
class SubClass:Super<String,String>{
String a;
public String mehod(String arv){
a=arv;
return a;
}
}
因为用户只实现了方法 public String mehod(String arv) ,而父类的method方法是 public Object mehoed(Object arv) ,因此覆盖失败。为了实现对method方法的覆盖,编译器在进行编译时,自动插入了一个桥接方法,桥接方法形式如下:
public Object mehoed(Object arv){
return method((String) arv);
}
桥接方法的方法签名和返回值和父类的泛型方法 public Object mehoed(Object arv) 一致,因此可以覆盖父类的method方法,实现了多态。通过桥接方法,还实现了对用户定义的method方法的调用,在程序员看来,就好像 public String mehoed(String arv) 成功覆盖了 public Object mehoed(Object arv) 一样。
正因为java是以这种比较奇怪的方式实现了泛型,因此一切new T(..)或者new T[]其实都会是new Object,没有任何意义,所以java干脆在编译阶段就禁止了这种语法,也就是说,泛型只能传入,不能产生。
除了不能产生泛型外,其它场合泛型和具体类型无差别,泛型参数可以用于类型转换、方法参数的类型等各种场合,比如这样 (T)a; 也是合法的。同时我们也可以发现,泛型参数的实例化,其实只是在编译阶段传入了类型信息,这个类型信息被用于“插入转换”和“方法桥接”。
但是需要注意的是,很多看起来像产生泛型的语法,其实并不是真正产生泛型,比如 List<String>=new ArrayList<String>() ,这里产生的只有一个ArrayList,并且这个ArrayList的类型参数被传入了一个String,这个String将用于ArrayList的“桥接方法”、“转换插入”等用处。实际上,在ArrayList<>的内部,是以一个Object[ ]来存放数据的,而不是String[ ],在你add("hahaha")的时候,这个泛型还是被传入的。在get(0)时,这个被传入的类型String在“转换插入”中发挥了作用,每次调用 get(0); 都被转换成了 (String)get(0); 。
上面说泛型在编译阶段就被“擦除”了,其实也不完全对,利用反射,仍然可以获取泛型信息,比如泛型参数、泛型限制、通配符信息等都可以获取到,说明在java的字节码(即.class文件)中,泛型信息仍然存在。因此,更准确的说,是泛型经过类加载器加载到虚拟机之后,泛型比擦除了,但是泛型的信息仍然存在字节码中,并且可以通过反射获取到。
java泛型中的各种限制的更多相关文章
- Java泛型中E、T、K、V等的含义
Java泛型中的标记符含义: E - Element (在集合中使用,因为集合中存放的是元素) T - Type(Java 类) K - Key(键) V - Value(值) N - Numbe ...
- Java泛型中extends和super的理解(转)
E – Element (在集合中使用,因为集合中存放的是元素) T – Type(Java 类) K – Key(键) V – Value(值) N – Number(数值类型) ? – 表示不确定 ...
- Java泛型中的标记符含义:
Java泛型中的标记符含义: E - Element (在集合中使用,因为集合中存放的是元素) T - Type(Java 类) K - Key(键) V - Value(值) N - Number( ...
- Java泛型中的通配符
Java泛型中的通配符可以直接定义泛型类型的参数.而不用把该函数定义成泛型函数. public class GenericsTest { public static void main(String[ ...
- Java泛型中的标记符含义
Java泛型中的标记符含义: E - Element (在集合中使用,因为集合中存放的是元素) T - Type(Java 类) K - Key(键) V - Value(值) N - Number ...
- Java泛型中的协变和逆变
Java泛型中的协变和逆变 一般我们看Java泛型好像是不支持协变或逆变的,比如前面提到的List<Object>和List<String>之间是不可变的.但当我们在Java泛 ...
- 【转】聊一聊-JAVA 泛型中的通配符 T,E,K,V,?
原文:https://juejin.im/post/5d5789d26fb9a06ad0056bd9 前言 Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型 ...
- java泛型中特殊符号的含义
java泛型中的标记符含义: E - Element (在集合中使用,因为集合中存放的是元素) T - Type(Java 类) K - Key(键) V - Value(值) N - Number ...
- Java泛型中extends和super的区别?
<? extends T>和<? super T>是Java泛型中的"通配符(Wildcards)"和"边界(Bounds)"的概念. ...
- Java泛型中<?> 和 <? extends Object>的异同分析
相信很多人和我一样,接触Java多年,却仍旧搞不清楚 Java 泛型中 <?>和 <? extends Object>的相似和不同.但是,这应该是一个比较高端大气上档次的Que ...
随机推荐
- vba实现excel多表合并
Excel多表合并之vba实现 需求 保留列名,复制每一个excel里的数据,合并到一个excel 操作步骤 将要合并的文件放在同一文件夹下,复制过来就好(ps:最好不要直接操作原数据文件,避免操作失 ...
- 2张图简单分析count(0)与count(*)
以前一直以为count(0)查询效率比count(*)比较高,原因大概是这么认为count(0)只是第一列进行统计,而count(*)所有列放在一起统计(亲,不要误会,这里不是所有列累加哦) 结果真的 ...
- C#操作excel打印
using System; using System.Data; using System.IO; using System.Runtime.InteropServices; using System ...
- mvc+EF - 有用文章
Mvc全局过滤器与Action排除:http://blog.csdn.net/shuaihj/article/details/53020428 MVC 自定义AuthorizeAttribute实现权 ...
- spring @Async 线程池使用
最近公司项目正逐渐从dubbo向springCloud转型,在本次新开发的需求中,全部使用springcloud进行,在使用时线程池,考虑使用spring封装的线程池,现将本次使用心得及内容记录下来 ...
- Kotlin 区间和循环 Loop和Range
在kotln中有一种很简单的方式来表达某一个区间的数字. 比如我想存储1~100. 我在其他的语言中可以用数组,步骤大概有三个把. 但是在kotlin中就只需要一步. 代码如下: fun main(a ...
- Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'blog.t_blog.addTime' which is not functi
sql报错: Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Expression #1 of SELECT ...
- 【锁】java 锁的技术内幕
转载自https://www.2cto.com/kf/201607/525119.html 一.基础知识 在Java并发编程里头,锁是一个非常重要的概念.就如同现实生活一样,如果房子上了锁.别人就进不 ...
- 深度学习TensorFlow常用函数
tensorflow常用函数 TensorFlow 将图形定义转换成分布式执行的操作, 以充分利用可用的计算资源(如 CPU 或 GPU.一般你不需要显式指定使用 CPU 还是 GPU, Tensor ...
- C++_类和动态内存分配6-复习各种技术及队列模拟
知识点: 队列:是一种抽象的数据类型(Abstract Data Type),可以存储有序的项目序列. 新项目被添加在队尾,并可以删除队首的项目.队列有些像栈.栈是在同一端进行添加和删除.这使得栈是一 ...