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 ...
随机推荐
- Graphics 小记
1.切图 drowg.DrawImage(productImg1, new System.Drawing.Rectangle(30, 30, 300, 300), new System.Drawing ...
- c#递归理解
什么是递归函数? 任何一个方法既可以调用其他方法又可以调用自己,而当这个方法调用自己时,我们就叫它递归函数或者递归方法! 说白了,就是调用自己. 通常递归有两个特点: 1.递归方法一直会调用自 ...
- [转]B+Tree图解
一, M阶B+树的定义(M阶是指一个节点最多能拥有的孩子数,M>2): 图1.1 3阶B+树 (1)根结点只有1个,分支数量范围[2,m]. (2)除根以外的非叶子结点,每个结点包含分支数 ...
- 所谓IIS未注册引起的故障及解决
- go-spew golang最强大的调试助手,没有之一
go内置的fmt.sprintf已经很强大了,但是和spew比起来还是相形见绌,这里来一个例子. import ( "fmt" "github.com/davecgh/g ...
- rsync实时备份备份服务搭建和使用指南
一.Rsync企业工作场景说明: 1.利用定时任务+rsync方式实现数据同步 对于网站内部技术人员创建的数据,可以采取定时任务的方式 2.利用实时任务+rsync方式实现数据同步 对于网站外部访问用 ...
- 数据库抽象层 pdo
一 . PDO的连接 $host = "localhost"; $dbname = "hejuntest"; $username = "root&qu ...
- Windowns DOS For 循环实例
update_all.bat代码示例: @echo off echo ***************************************************************** ...
- ajax的get,post ,封装
let ajax = new Object(); ajax.get = function(url,fn){ //创建ajax对象 let xhr = new XMLHttpRequest(); //与 ...
- 【bzoj4832】[Lydsy1704月赛]抵制克苏恩 期望dp
Description 小Q同学现在沉迷炉石传说不能自拔.他发现一张名为克苏恩的牌很不公平.如果你不玩炉石传说,不必担心,小Q 同学会告诉你所有相关的细节.炉石传说是这样的一个游戏,每个玩家拥有一个 ...