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泛型中的各种限制的更多相关文章

  1. Java泛型中E、T、K、V等的含义

     Java泛型中的标记符含义:  E - Element (在集合中使用,因为集合中存放的是元素) T - Type(Java 类) K - Key(键) V - Value(值) N - Numbe ...

  2. Java泛型中extends和super的理解(转)

    E – Element (在集合中使用,因为集合中存放的是元素) T – Type(Java 类) K – Key(键) V – Value(值) N – Number(数值类型) ? – 表示不确定 ...

  3. Java泛型中的标记符含义:

    Java泛型中的标记符含义: E - Element (在集合中使用,因为集合中存放的是元素) T - Type(Java 类) K - Key(键) V - Value(值) N - Number( ...

  4. Java泛型中的通配符

    Java泛型中的通配符可以直接定义泛型类型的参数.而不用把该函数定义成泛型函数. public class GenericsTest { public static void main(String[ ...

  5. Java泛型中的标记符含义

    Java泛型中的标记符含义:  E - Element (在集合中使用,因为集合中存放的是元素) T - Type(Java 类) K - Key(键) V - Value(值) N - Number ...

  6. Java泛型中的协变和逆变

    Java泛型中的协变和逆变 一般我们看Java泛型好像是不支持协变或逆变的,比如前面提到的List<Object>和List<String>之间是不可变的.但当我们在Java泛 ...

  7. 【转】聊一聊-JAVA 泛型中的通配符 T,E,K,V,?

    原文:https://juejin.im/post/5d5789d26fb9a06ad0056bd9 前言 Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型 ...

  8. java泛型中特殊符号的含义

    java泛型中的标记符含义:  E - Element (在集合中使用,因为集合中存放的是元素) T - Type(Java 类) K - Key(键) V - Value(值) N - Number ...

  9. Java泛型中extends和super的区别?

    <? extends T>和<? super T>是Java泛型中的"通配符(Wildcards)"和"边界(Bounds)"的概念. ...

  10. Java泛型中<?> 和 <? extends Object>的异同分析

    相信很多人和我一样,接触Java多年,却仍旧搞不清楚 Java 泛型中 <?>和 <? extends Object>的相似和不同.但是,这应该是一个比较高端大气上档次的Que ...

随机推荐

  1. Java集合类总结 (一)

    集合类中的基本接口 集合类中最基础的接口是Collection: public interface Collection<E> { boolean add(E element); Iter ...

  2. Android 应用检查更新并下载

    1.在Android应用当中都有应用检查更新的要求,往往都是在打开应用的时候去更新下载. 实现的方法是:服务器端提供接口,接口中可以包含在最新APK下载的URL,最新APK的VersionCode,等 ...

  3. mvc - codefirst 数据迁移

    from :http://blog.csdn.net/xiaoyiyz/article/details/41485325

  4. 630. Course Schedule III

    There are n different online courses numbered from 1 to n. Each course has some duration(course leng ...

  5. 【转】在Win10家庭版中启用组策略

    源地址:https://www.baidu.com/link?url=tZrD7LVxQEKQUTWUum86LoxyaxWNLs5BeBE2K36TliRi8sjGraKc-iP3TEm6sc_KX ...

  6. Python DataFrame 如何删除原来的索引,重新建立索引

    删除行索引重排: ser.reset_index(drop = True) df.reset_index(drop = True) ---------------------------------- ...

  7. python2和python3 分别连接MySQL的代码

    python2中的写法如下: #coding=utf-8 import MySQLdb try: conn = MySQLdb.connect(host='localhost', port=3306, ...

  8. css3中的自定义字体

    自定义字体 /*定义*/ @font-face { font-family: "icons"; src: url("icomoon.eot"); src: lo ...

  9. 微信小程序之页面之间传递值

    页面之间传值有三种方式 1.url传值 2.本地存储传值 3.全局变量传值 1.url传值: 通过url传值的需要通过option来获取参数值. 更多详情可以访问小程序-navigateTo章节. A ...

  10. Markdown 语法快速入门手册

    Markdown 是一种轻量级标记语言,能将文本换成有效的XHTML(或者HTML)文档,它的目标是实现易读易写,成为一种适用于网络的书写语言. Markdown 语法简洁明了,易于掌握,所以用它来写 ...