java MS之泛型

http://blog.csdn.net/stypace/article/details/42102567

1、泛型基本概念

1.1、由来

泛型是JDK 1.5的一项新特性,在Java语言处于还没有出现泛型的版本时,只能通过Object是所有类型的父类和类型强制转换两个特点的配合来实现类型泛化。例如在哈希表的存取中,JDK 1.5之前使用HashMap的get()方法,返回值就是一个Object对象,由于Java语言里面所有的类型都继承于java.lang.Object,那Object转型为任何对象成都是有可能的。但是也因为有无限的可能性,就只有程序员和运行期的虚拟机才知道这个Object到底是个什么类型的对象。在编译期间,编译器无法检查这个Object的强制转型是否成功,如果仅仅依赖程序员去保障这项操作的正确性,许多ClassCastException的风险就会被转嫁到程序运行期之中。

1.2、伪泛型

泛型技术在C#和Java之中的使用方式看似相同,但实现上却有着根本性的分歧,C#里面泛型无论在程序源码中、编译后的IL中(Intermediate Language,中间语言,这时候泛型是一个占位符)或是运行期的CLR中都是切实存在的,List<int>与List<String>就是两个不同的类型,它们在系统运行期生成,有自己的虚方法表和类型数据,这种实现称为类型膨胀,基于这种方法实现的泛型被称为真实泛型。

  Java语言中的泛型则不一样,它只在程序源码中存在,在编译后的字节码文件中,就已经被替换为原来的原始类型(Raw Type,也称为裸类型)了,并且在相应的地方插入了强制转型代码,因此对于运行期的Java语言来说,ArrayList<int>与ArrayList<String>就是同一个类。所以说泛型技术实际上是Java语言的一颗语法糖,Java语言中的泛型实现方法称为类型擦除,基于这种方法实现的泛型被称为伪泛型。

1.3、泛型的使用

  • 泛型类
  • 泛型接口
  • 泛型方法

1.4、一些要求及规则

  • 不能使基本类型
  • 不管该限定是类还是接口,统一都使用关键字extends
  • 可以使用&符号给出多个限定
  •  如果限定既有接口也有类,那么类必须只有一个,并且放在首位置

详见:http://blog.csdn.net/lonelyroamer/article/details/7864531

2、原理

2.1、类型擦除

Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会在编译器在编译的时候去掉。这个过程就称为类型擦除。

如在代码中定义的List<object>和List<String>等类型,在编译后都会编程List。JVM看到的只是List,而由泛型附加的类型信息对JVM来说是不可见的。

2.2、重要:用反射来看泛型的机制(甚至可以破坏)

在程序中定义了一个ArrayList泛型类型实例化为Integer的对象,如果直接调用add方法,那么只能存储整形的数据。不过当我们利用反射调用add方法的时候,却可以存储字符串。这说明了Integer泛型实例在编译之后被擦除了,只保留了原始类型。

2.3、原始类型

原始类型(raw type)就是擦除去了泛型信息,最后在字节码中的类型变量的真正类型。无论何时定义一个泛型类型,相应的原始类型都会被自动地提供。类型变量被擦除(crased),并使用其限定类型(使用extends的,如果extends多个则原始类型就用第一个边界的类型变量来替换)替换,无限定的变量用Object替换。

3、类型擦除引起的问题及解决办法

3.1、先检查、再编译

因为类型擦除是在编译期完成的,在运行的时候就会忽略泛型,为了保证在运行的时候不出现类型错误,就需要在编译之前就检查是否满足泛型要求(类型检查)。

3.2、类型检查的依据

以上两种情况都没有错误:第一种情况,在使用arrayList1的时候与完全使用泛型参数一样的效果,因为new ArrayList()只是在内存中新开辟一个存储空间,它并不能判断类型,而真正涉及类型检查的是它的引用,所以在调用arrayList1的时候会进行类型检查。同理,第二种情况,就不会进行类型检查。

3.3、泛型参数化类型没有继承关系

  • 第一种情况,可以扩展为一下形式:

假设它编译没错,那么当我们使用arrayList2引用用get()方法取值的时候,返回的都是String类型的对象(类型检测是根据引用来决定的),可是它里面实际上已经被我们存放了Object类型的对象,这样就会出现ClassCastException。

  • 第二种情况,同样扩展为:

虽然不会出现ClassCastException,但是假设它编译没错,那么当我们使用arrayList2引用用get()方法取值的时候,返回的都是Object类型的对象,需要进行类型转换才行,这样,泛型就完全没有作用了。

4、类型擦除与多态的冲突和解决方法

现在看来我们在子类中重写了父类的两个方法,而实际上,经过类型擦除之后:

可以看到,父类和子类的方法中参数类型不同,所以如果是在普通的继承关系中,这完全不是重写,而是重载;但是如果在泛型中呢?

如果是重载,那么子类中两个setValue方法,一个是参数Object类型,一个是Date类型,可是我们发现,根本就没有这样的一个子类继承自父类的Object类型参数的方法。所以说,在泛型中确实是重写了,而不是重载。

4.1、桥方法

         通过编译源代码会发现DataInter最后会有四种方法,其中两个是编译器自己生成的桥方法,它的参数类型是Object,也就是说,子类中真正覆盖/重写父类两个方法的就是这两个我们看不到的桥方法。而打在我们自己定义的setvalue和getValue方法只不过是假象。而桥方法的内部实现,就只是去调用我们自己重写的那两个方法。

5、泛型在静态类和静态方法中的问题

泛型类中的静态方法和静态变量不可以使用泛型类所声明的泛型类型参数。

因为泛型类中的泛型参数的实例化是在定义对象的时候指定的,而静态变量和静态方法不需要使用对象来调用。对象都没有创建,如何确定这个泛型参数是何种类型,所以当然是错误的。

因为这是一个泛型方法,在泛型方法中使用的T是自己在方法中定义的T,而不是泛型类中的T。调用这个方法时会声明T类型的。

版权声明:本文为博主原创文章,未经博主允许不得转载。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏塔奇克马敲代码

第 18 章 用于大型程序的工具

14320
来自专栏个人随笔

房上的猫:数组

一.数组:  1.定义:   (1)数组就是一个变量,用于将相同数据类型的数据储存在内存中   (2)数组中的每一个数据元素都属于统一数据类型  2.基本要素:...

36690
来自专栏林冠宏的技术文章

由 System.arraycopy 引发的巩固:对象引用 与 对象 的区别

首先明确一点,System.arraycopy 操作的是数组,效果是深复制。 是不是觉得怎么和你印象的中不一样? 重点来了,对于对象数组,例如: User[]...

14540
来自专栏CodeSheep的技术分享

Java编程思想学习录(连载之:内部类)

227110
来自专栏好好学java的技术栈

“面试不败计划”: java语言基础面试题(三)

13230
来自专栏编程

《4》python数据类型和变量

(4)python数据类型和变量 ? 整数 Python可以处理任意大小的整数,例如:1,100,-8080,0,等等。 十六进制用0x前缀和0-9,a-f表示...

35890
来自专栏Vamei实验室

Python进阶04 函数的参数对应

我们已经接触过函数(function)的参数(arguments)传递。当时我们根据位置,传递对应的参数。我们将接触更多的参数传递方式。 回忆一下位置传递: d...

21070
来自专栏MyBlog

Effective.Java 读书笔记(1)静态工厂和构造方法

用户在获得类它本身的实例的时候,通常会想到的就是使用public的构造器,但是一个类可以提供一个public的工厂方法。 这种工厂方法简化了返回该类实例的静态...

11320
来自专栏我的技术专栏

C++ 异常机制分析

21140
来自专栏工科狗和生物喵

【计算机本科补全计划】Java学习笔记(六) 循环+分支结构

正文之前 这两节太弱了。基本大一的C++程序设计课就足够对付理解了。所以还是水一波吧,实在没办法,整个教程还是全部抄全吧。免得到时候新人小白入门的时候还要去看别...

37190

扫码关注云+社区

领取腾讯云代金券