Java:泛型
一、序言
变化一:
在引入范型之前,Java中的类型分为原始类型、复杂类型,其中复杂类型分为数组和类;引入范型后,一个复杂类型可以细分成更多的类型。
例如,原先的List类型,现在细分成List<Object>, List<String>等更多的类型。
注:List<Object>和List<String>是两种不同的类型, 它们之间没有继承关系,即使String继承了Object。下述代码是非法的:
- List<String> ls = new ArrayList<String>();
- List<Object> lo = ls;
这样设计的原因:根据lo的声明,编译器允许你向lo中添加任意对象(例如Integer),但是为其赋值List<String>类型的值,破坏了数据类型的完整性。
变化二:
在引入范型之前,要让类中的方法支持多个数据类型,就需要对方法进行重载;在引入范型之后,可以更进一步定义多个参数以及返回值之间的关系。
例如,public void write(Integer i, Integer[] ia);及public void write(Double d, Double[] da);的范型版本为:public <T> void write(T t, T[] ta);
二、详述Java泛型
在细说Java泛型之前,大家可以先看一下如下这样一段代码,相信大家肯定不会感觉陌生:
- package com.soft;
- public class WW {
- public <T> void write(T t, T[] ta){
- }
- }
在编码时,可经常见到类似于上面这样的代码,接下来将详述Java泛型。
那么我们首先要知道什么是泛型,泛型即“参数化类型”,顾名思义,就是将类型由原来具体的类型变为参数化的形式。在定义泛型接口、泛型类和泛型方法的过程中,我们常见的T、E、S等形式的参数用于表示泛型形参,用于接收来自外部使用时候传入的类型实参。
A、自定义泛型类和泛型方法
(1)定义带类型参数的类
- package com.soft;
- public class MM<T, S extends T> {
- public T say(){
- T t=null;
- return t;
- }
- }
规则:在定义带类型参数的类时,在紧跟类命之后的<>内,指定一个或多个类型参数的名字,同时也可以对类型参数的取值范围进行限定,多个类型参数之间用","进行分隔。
说明:定义完类型参数后,可以在类中定义位置之后的几乎任意地方使用类型参数(静态块,静态属性,静态方法除外),就像使用普通的类型一样。
注意:父类定义的类型参数不能被子类继承。
(2)定义带类型参数方法
- package com.soft;
- public class WW {
- public <T> void write(T t, T[] ta){
- }
- public <S> void read(S s, S[] sa){
- }
- public <T, S extends T> T test(T t, S s){
- S ss=null;
return ss;- }
- }
规则:在定义带类型参数的方法时,在紧跟可见范围修饰(如public)之后的<>内,指定一个或多个类型参数的名字,同时也可以对类型参数的取值范围进行限定,多个类型参数之间用","进行分隔。
说明: 定义完带类型参数的方法后,可以在方法中定义位置之后的任意地方使用类型参数,就像使用普通的类型一样。
注意:定义带类型参数的方法,其主要目的是为了表达多个参数以及返回值之间的关系。例如本例中T和S为继承关系,返回值的类型(T)和第一个类型参数(t)的类型(T)相同。
补充:
当我们需要一个在逻辑上可以用来表示同时是List<Integer>和List<Number>的类型时,类型通配符应运而生。类型通配符一般是使用"?"代替具体的类型实参。
如果仅仅是想实现多态,请优先使用通配符解决。
初始代码如下:
- public <T> void test1(List<T> u){
- ...
- }
- public <S> void test2(List<S> u){
- ...
- }
使用通配符的代码如下:
- public void test(List<?> s){
- ...
- }
B、泛型类和泛型方法的使用
(1)对带类型参数的类进行赋值
对带类型参数的类进行赋值有两种方式:
A、声明类变量或者实例化时,如下:
- List<String> list;
- list = new ArrayList<String>();
如下赋值方式会报错:
- List<Integer> a = new ArrayList<Integer>(712);
- List<Number> b = a;
List<Number>不能视为List<Integer>的父类,请参照前面“序言->变化一”中的描述。
B、继承类或者实现接口时,如下:
- public class MyList<E> extends ArrayList<E> implements List<E> {...}
(2)对带类型参数的方法进行赋值
当调用泛型方法时,编译器自动对类型参数进行赋值,当不能成功赋值时报编译错误,如下:
- package com.soft;
- import java.util.List;
- public class WW {public <T, S extends T> T test(T t, S s){
- return s;
- }
- public <T> T test1(T t, List<T> list){
- return t;
- }
- public <T> T test2(List<T> list1, List<T> list2){
- return null;
- }
- public void common(){
- Object o = null;
- Integer i = null;
- Number n = null;
- test(n, i);//此时T为Number, S为Integer
- test(o, i);//T为Object, S为Integer
- List<Integer> list1 = null;
- List<Number> list2 = null;
- test1(i, list1);//此时T为Integer,i为Integer类型,list1为List<Integer>类型
- test1(i, list2);//此时T为Number,i为Integer类型,list2为List<Number>类型
- test2(list1, list2);//编译报错
- }
- }
请读者自行分析上述代码中着色语句为什么会编译报错,很简单的哦O(∩_∩)O哈哈~
(3)对通配符类型参数进行赋值
在上面两小节中,对是类型参数赋予具体的值,除此,还可以对类型参数赋予不确定值
假设有如下定义:
- List<?> unknownList;
- List<? extends Number> unknownNumberList;
- List<? super Integer> unknownBaseLineIntgerList;
现对上述定义的变量赋值,如下:
- List<String> listString = null;
- unknownList = listString;
三、类型通配符上限与下限
有时候,我们会听到类型通配符上限和类型通配符下限这样的说法,其实在前面的例子中有下面这样一小段代码,在该代码中已经体现出一些上下限的思想
- public <T, S extends T> T test(T t, S s){
- S ss=null;
- return ss;
- }
此处我们进一步说明
- package com.soft;
- public class GenericTest {
- public static void main(String[] args) {
- Student<String> name = new Student<String>("xiaoming");
- Student<Integer> age = new Student<Integer>(24);
- Student<Number> number = new Student<Number>(1001);
- getData(name);
- getData(age);
- getData(number);
- getUpperNumberData(name);//编译报错
- getUpperNumberData(age);
- getUpperNumberData(number);
- }
- public static void getData(Student<?> data) {
- System.out.println("data :" + data.getData());
- }
- public static void getUpperNumberData(Student<? extends Number> data){//此处限定类型实参只能是Number类及其子类
- System.out.println("data :" + data.getData());
- }
- }
- class Student<T> {
- private T data;
- public Student() {
- }
- public Student(T data) {
- this.data = data;
- }
- public T getData() {
- return data;
- }
- }
我想大家通过上例肯定能体会到什么是类型通配符上限,类型通配符上限通过形如Student<? extends Number>形式定义,相对应的,类型通配符下限为Student<? super Number>形式,其含义与类型通配符上限正好相反,在此不作过多阐述了。
四、注意事项
A、当对类或方法的类型参数进行赋值时,要求对所有的类型参数进行赋值,否则,将得到一个编译错误。
B、在Java集合框架中,对于参数值是未知类型的容器类,只能读取其中的元素,不能向其中添加元素,因为其类型是未知,编译器无法识别添加元素的类型和容器的类型是否兼容,唯一的例外是NULL
C、可以使用带泛型参数的类声明数组,却不可以用于创建数组
- List<Integer>[] iListArray;
- iListArray=new ArrayList[10];
- iListArray=new ArrayList<Integer>[10];//编译时错误
五、实现原理
(1)一个泛型类被其所有的实例共享
现有如下这样一段代码,其打印的结果是什么?
- List<String> l1 = new ArrayList<String>();
- List<Integer> l2 = new ArrayList<Integer>();
- System.out.println(l1.getClass() == l2.getClass());
或许你会说是false,but,you are wrong!它打印出true。我们可以发现虽然传入的类型实参不同,但其生成的所有对象实例的类型是一样的,即一个泛型类的所有实例在运行时具有相同的运行时类(class), 而不管他们的实际类型参数。
事实上,泛型之所以叫泛型,就是因为它对其所有可能的类型参数有同样的行为。类的静态变量和静态方法在所有的实例间共享,这就是为什么在静态变量的声明和初始化时或者在静态方法或静态初始化代码中使用类型参数是不合法的原因,类型参数是属于具体实例的。
(2)擦除(erasure)
在使用泛型类时,虽然传入了不同的泛型实参,但并没有真正意义上生成不同的类型,可以接收不同泛型实参的泛型类在内存中只有一个,是原来的最基本的类型(上例中为List),当然,在逻辑上我们可以理解成多个不同的泛型类型。
究其原因,Java泛型是通过java编译器的称为擦除(erasure)的前端处理来实现的,Java中的泛型只作用于代码编译阶段,在编译过程中正确检验泛型结果后,会将泛型的相关信息擦除(erasure),也就是说,成功编译之后的.class文件中是不包含任何泛型信息的,泛型信息不会进入运行时阶段。你可以把它认为是(基本上就是)一个从源码到源码的转换,它把泛型版本转换成非泛型版本。
基本上,擦除(erasure)去掉了所有的泛型类型信息,所有在尖括号之间的类型信息都被扔掉了,比如说,一个 List<String>类型被转换为List,所有对类型变量的引用被替换成类型变量的上限(通常是Object)。对此总结成一句话:泛型类型在逻辑上可以看成是多个不同的类型,但实际上都是相同的基本类型。
(3)类型转换
类型参数在运行时并不存在,这意味着它们不会添加任何时间或者空间上的负担,这很好,不幸的是,这也意味着你不能依靠它们进行类型转换,下面这段代码有如下图所示的报错信息:
- public <T> T badCast(T t, Object o) {
- return (T) o; // unchecked warning
- }
类似的,如下的类型转换将得到一个unchecked warning,因为运行时环境不会为你做这样的检查。
- Collection cs = new ArrayList<String>();
- Collection<String> cstr = (Collection<String>) cs;
(4)instanceof
泛型类被其所有实例(instances)共享的另一个暗示就是检查一个实例是不是某一个特定类型的泛型类是没有意义的,如下图所示:
六、Class泛型的使用
Java5中的一个变化是类java.lang.Class是泛型化的(Class<T>),这是把泛型扩展到容器类之外的一个很有意思的例子。 你很可能会问,Class的类型参数T代表什么?它代表Class对象代表的类型,比如说,Class对象String.class的类型为Class<String>,Class对象Serializable.class的类型为Class<Serializable>,此处T代表什么请自行体会。
Java5为什么会有这样的变化,这可以被用来提高你反射代码的类型安全。特别的,因Class的newInstance()方法返回一个T,你可以在使用反射创建对象时得到更精确的类型。比如说,假定你要写一个工具方法来进行数据库查询,给定一个SQL语句,请返回一个数据库中符合查询条件的对象集合(collection),你该怎么做?
解决方案:
(1)定义接口:
- interface Factory<T> {
- public T[] make();
- }
(2)定义查询方法:
- public <T> Collection<T> select(Factory<T> factory, String statement) {
- Collection<T> result = new ArrayList<T>();
- /* run sql query using jdbc */
- for ( int i=0; i<10; i++ ) { /* iterate over jdbc results */
- T item = factory.make();
- /* use reflection and set all of item’s fields from sql results */
- result.add( item );
- }
- return result;
- }
(3)调用(匿名类方案 ):
- select(new Factory<EmpInfo>(){
- public EmpInfo make() {
- return new EmpInfo();
- }
- } , ”selection string”);
除了上述方式外,也可以声明一个类来支持Factory接口:
- class EmpInfoFactory implements Factory<EmpInfo> { ...
- public EmpInfo make() { return new EmpInfo();}
- }
- select(getMyEmpInfoFactory(), "selection string");//调用语句,其中getMyEmpInfoFactory()方法返回自定义的支持Factory接口的类
解说:上述两种解决方案虽然可行,却很不自然,其缺点是它们需要下面的二者之一:
a.为每个要使用的类型定义冗长的匿名工厂类;
b.为每个要使用的类型声明一个工厂类并传递其对象给调用处
此时可以选择使用class类型参数,它可以被反射使用,没有泛型的代码可能如下:
- Collection emps = sqlUtility.select(EmpInfo.class, ”select * from emps”);
- public static Collection select(Class c, String sqlStatement) {
- Collection result = new ArrayList();
- /* run sql query using jdbc */
- for ( /* iterate over jdbc results */ ) {
- Object item = c.newInstance();
- /* use reflection and set all of item’s fields from sql results */
- result.add(item);
- }
- return result;
- }
上述方案虽然能达到目的,却不能给我们返回一个精确类型的集合,因Java5及以后版本中Class是泛型的,我们可以按下面这种方式写:
- Collection<EmpInfo> emps=sqlUtility.select(EmpInfo.class, ”select * from emps”);
- public static <T> Collection<T> select(Class<T>c, String sqlStatement) {
- Collection<T> result = new ArrayList<T>();
- /* run sql query using jdbc */
- for ( /* iterate over jdbc results */ ) {
- T item = c.newInstance();
- /* use reflection and set all of item’s fields from sql results */
- result.add(item);
- }
- return result;
- }
上述方案通过一种类型安全的方式得到了我们想要的集合,这项技术是一个非常有用的技巧。
七、参考资料
本文仅简单阐述了Java泛型的部分内容,此处贴出一些优质文章以供读者阅览
(1)http://www.cnblogs.com/lwbqqyumidi/p/3837629.html
Java:泛型的更多相关文章
- Java泛型的历史
为什么Java泛型会有当前的缺陷? 之前的章节里已经说明了Java泛型擦除会导致的问题,C++和C#的泛型都是在运行时存在的,难道Java天然不支持“真正的泛型”吗? 事实上,在Java1.5在200 ...
- 浅析Java 泛型
泛型是JavaSE5引入的一个新概念,但是这个概念在编程语言中却是很普遍的一个概念.下面,根据以下内容,我们总结下在Java中使用泛型. 泛型使用的意义 什么是泛型 泛型类 泛型方法 泛型接口 泛型擦 ...
- Java:泛型基础
泛型 引入泛型 传统编写的限制: 在Java中一般的类和方法,只能使用具体的类型,要么是基本数据类型,要么是自定义类型.如果要编写可以应用于多种类型的代码,这种刻板的限制就会束缚很多! 解决这种限制的 ...
- java泛型基础
泛型是Java SE 1.5的新特性, 泛型的本质是参数化类型, 也就是说所操作的数据类型被指定为一个参数. 这种参数类型可以用在类.接口和方法的创建中, 分别称为泛型类.泛型接口.泛型方法. Ja ...
- 使用java泛型设计通用方法
泛型是Java SE 1.5的新特性, 泛型的本质是参数化类型, 也就是说所操作的数据类型被指定为一个参数. 因此我们可以利用泛型和反射来设计一些通用方法. 现在有2张表, 一张user表和一张stu ...
- 关于Java泛型的使用
在目前我遇到的java项目中,泛型应用的最多的就属集合了.当要从数据库取出多个对象或者说是多条记录时,往往都要使用集合,那么为什么这么使用,或者使用时有什么要注意的地方,请关注以下内容. 感谢Wind ...
- 初识java泛型
1 协变数组类型(covariant array type) 数组的协变性: if A IS-A B then A[] IS-A B[] 也就是说,java中的数组兼容,一个类型的数组兼容他的子类类型 ...
- 【Java心得总结四】Java泛型下——万恶的擦除
一.万恶的擦除 我在自己总结的[Java心得总结三]Java泛型上——初识泛型这篇博文中提到了Java中对泛型擦除的问题,考虑下面代码: import java.util.*; public clas ...
- 【Java心得总结三】Java泛型上——初识泛型
一.函数参数与泛型比较 泛型(generics),从字面的意思理解就是泛化的类型,即参数化类型.泛型的作用是什么,这里与函数参数做一个比较: 无参数的函数: public int[] newIntAr ...
- 初识Java泛型以及桥接方法
泛型的由来 在编写程序时,可能会有这样的需求:容器类,比如java中常见的list等.为了使容器可以保存多种类型的数据,需要编写多种容器类,每一个容器类中规定好了可以操作的数据类型.此时可能会有Int ...
随机推荐
- Web环境使用相对路径发布Webservice
常我们的Webservice服务的发布地址都将是一个相对路径,在与Spring一起使用时我们需要引入Cxf配置Webservice的schema,如jaxws,用以定义对应的Webservice. & ...
- Highcharts使用简例 + 异步动态读取数据
第一部分:在head之间加载两个JS库. <script src="html/js/jquery.js"></script> <script src= ...
- my_strcat()
char* my_strcat(char* S1,const char* S2){ //严格符合strcat()的接口形式,需要的S1空间是两个字符串空间总和-1. int i=0,j=0; whil ...
- 命令行向php传入参数的两种方法
##$argv or $argc $argv 包含当运行于命令行下时传递给当前脚本的参数的数组. $argv[0] 就是脚本文件名. $argc 包含当运行于命令行下时传递给当前脚本的参数的数目 ...
- ZIP打包解包
linux zip命令的基本用法是: zip [参数] [打包后的文件名] [打包的目录路径] linux zip命令参数列表: -a 将文件转成ASCII模式-F 尝试修复损坏的压缩文件-h 显示帮 ...
- SCCM 客户端的修复
1. Stopping the SMS Agent Host service (net stop ccmexec) 2. Stopping the WMI service (net stop winm ...
- STM32的USART
转载自:http://www.cnblogs.com/TrueElement/archive/2012/09/14/2684298.html 几个问题: 1.状态寄存器(USART_SR)中的TC(T ...
- Hadoop2.6 datanode配置在线更新
datanode 的配置可以在线更新了,http://blog.cloudera.com/blog/2015/05/new-in-cdh-5-4-how-swapping-of-hdfs-datano ...
- JavaWeb学习----Cookie实现记住密码的功能
[声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/4 ...
- C# WebService URL重写
背景 有时候我们会有这样的需求,将 WebService URL 中的 asmx 后缀去掉:或者我们要模拟普通 Web 的 URL,接口名称直接拼接在 URL 中.这些情况我们都要用到URL重写. 关 ...