java泛型——新手研究和总结
2017-08-21 12:13
369 查看
学习Java知识,肯定会接触到泛型的学习,菜猫我这周在准备一个泛型方法时候,突然发现自己不熟,记得以前明明知道,所以作为有一个伟大志向(搬砖)的我特意重新学习,为了加深印象,特来写这篇博客,把涉及到原理和理解当做笔记记录下来。
泛型是JDK1.5新增的特性,是泛型程序设计的一种应用,在此之前,泛型程序设计是用继承来实现,如ArrayList用一个Object[]数组来维持列表中的对象以适应不同类型的对象操作,这样会可能导致操作不同类型对象而出现错误,而泛型可以将数据的类型变成一个参数,用于类、接口和方法的创建中(对此分别称为泛型类、泛型接口和泛型方法)用来操作同一种数据类型。
在上述示例过程中,分别通过引用一个和两个类型变量来表示对应类的操作对象类型,已达到限制操作对象类型的作用,在对象实例化过程中,如果不引人操作类型,如:
则默认的操作的类型是Object类型,因此在使用过程中应传输类型变量来限制其操作类型,如操作String类型,则写成
同时,对于类型变量一般采用“<>”来声明,一般采用大写字母表示,在Java库中,使用变量E代表集合的元素类型,使用K和V表示键值,使用T来表示任意类型(可用U,S等来表示多个类型变量)。
输出结果为:
在上述示例中通过在普通类中定义泛型方法时,需要用类型变量对方法中要操作的对象类型进行声明(不然突然一个T,编译器怎么能够明白这是要泛型操作呢),同时注意的时,一般大多数情况下,编译器是能够根据所传参数等信息自动识别出返回类型,但是当所传参数类型不一样的时候,默认会选择各个参数类型的共同超类型(但从一些资料上了解,当参数继承多个类时,可能会出现错误,但是自测目前唯一想到的就是可能出现类型转换错误,如:
该示例中,Interger、Double同时继承Number和Comparable,所以可以编译能够根据共同的超类型确定引入的操作类型)。
在这里可以发现泛型类的继承和一般类的继承关系一样,不会因为类型变量的关系而发生改变,其实从类型擦除也可以理解,将泛型类ArrayList进行类型擦除后是同一个类,就不存在继承关系。但通配符不一样,可以达到这一效果。首先声明其定义如下:
在此注意的是通配符类型“?”不是类型变量,因此不能定义泛型类和泛型接口,只能用作函数的返回值,而类型变量还能够对对象的实域类型进行声明。通配符不仅可以对下进行限定,也可以对上进行限定,甚至是不用限定,然而不管限定符怎样设置,其方法处理的都是ClassA对象,并且和类型变量限定不同的是,在类型变量限定中,是可以使用“&”对多个实现的接口或类进行处理,但是通配符限定只能对一个接口或类进行处理,因此以下几种方式声明都是错误的:
通过通配符类型,同一个泛型类可以处理具有继承关系类型的泛型类之间的转换,而直接通过类型变量限定会出错,如下:
正确的做法可以如下:
通过这种方式就可以实现类型转换,其关系图如下:
因此从上述知识可以发现,通过合理的使用类型变量限定和通配符,可以很好的控制参数类型范围,如
但是在SUN JDK1.6版本中,也可以根据方法的返回值类型来区别不同的方法
在引用[3]的解释是说虽然方法重载的特征签名不包括方法的返回值类型,但是在Class文件格式中,只要描述符不是完全一致的两个方法就可以共存到一个Class文件中,在方法调用时,引人Signature来存储一个字节码层面的特征签名(代码层特征签名只包含方法名称、参数顺序和参数类型,而字节码的特征签名包含方法的返回值及受查异常表),因此可以根据类型变量调用对应的方法。然而这种情况在大多数编译器上是拒绝编译的。因此当需要使用多态时,可以考虑采用桥方法。
因为数组可以记住其元素类型,因此前两种方式应该是允许进行类型转换,将创建Class对象数组转换为具体的类型变量的泛型数组,而最后一种方式应该是不能允许的,这样可以以防出现类型转化错误。。
因为类中的静态方法和静态域是属于类而不属于对象的,是类的对象共有的,因此当泛型进行类型擦除之后,只剩下静态域和方法而没有声明其类型,因此导致程序无法运行。
但是可以在异常规范中使用类型变量,如下方式就是合法的:
在这里编译会把这个异常当做未检查的异常进行处理。
引用:
【1】http://www.cnblogs.com/lwbqqyumidi/p/3837629.html
【2】java核心技术
【3】深入理解java虚拟机
泛型是JDK1.5新增的特性,是泛型程序设计的一种应用,在此之前,泛型程序设计是用继承来实现,如ArrayList用一个Object[]数组来维持列表中的对象以适应不同类型的对象操作,这样会可能导致操作不同类型对象而出现错误,而泛型可以将数据的类型变成一个参数,用于类、接口和方法的创建中(对此分别称为泛型类、泛型接口和泛型方法)用来操作同一种数据类型。
Java泛型的基本知识
1. 泛型类定义和创建
泛型类是具有一个或者多个类型变量的类。声明如下:public class ClassA <T>{ //用<>来引入一个类型变量T,在具体实现时可传人具体要操作的类型来限制ClassA的操作,否则会一律当做Object来处理 T filed1; public T getFiled1(){ if(filed1==null){ return null; } return filed1; } public void setFiled1(T param){ this.filed1=param; } }
public class ClassB<T,U> { //传人两个操作类型,不能一样 T filed1; U filed2; }
在上述示例过程中,分别通过引用一个和两个类型变量来表示对应类的操作对象类型,已达到限制操作对象类型的作用,在对象实例化过程中,如果不引人操作类型,如:
ClassA a=new ClassA();
则默认的操作的类型是Object类型,因此在使用过程中应传输类型变量来限制其操作类型,如操作String类型,则写成
ClassA<String> a=new ClassA<>();
同时,对于类型变量一般采用“<>”来声明,一般采用大写字母表示,在Java库中,使用变量E代表集合的元素类型,使用K和V表示键值,使用T来表示任意类型(可用U,S等来表示多个类型变量)。
2.泛型方法定义和构建
泛型方法可以定义在普通类中,也可以定义在泛型类中,在泛型类的定义如上示例,下面主要示例如何在普通类中声明和调用:public class ClassC { public static <T> T getT(T... t){ return t[0]; } public static <K,V> V getValueOfKey(Map<K,V> map, K k){ return map.get(k); } }
public class Main { public static void main(String[] args) { String testC=ClassC.<String>getT("abc","bbc"); //标准是加入类型限定符<String> System.out.println(testC); Map<String,String> map=new HashMap<>(); map.put(testC,"TESTC"); System.out.println(ClassC.getValueOfKey(map,testC)); //不加类型限定符也是可以,大多数情况下编译器会自动能够识别,但可能也有例外 } }
输出结果为:
abc TESTC
在上述示例中通过在普通类中定义泛型方法时,需要用类型变量对方法中要操作的对象类型进行声明(不然突然一个T,编译器怎么能够明白这是要泛型操作呢),同时注意的时,一般大多数情况下,编译器是能够根据所传参数等信息自动识别出返回类型,但是当所传参数类型不一样的时候,默认会选择各个参数类型的共同超类型(但从一些资料上了解,当参数继承多个类时,可能会出现错误,但是自测目前唯一想到的就是可能出现类型转换错误,如:
double a=ClassC.getT(6.02,5.3,19,21);//错误,这么写编译不了 Number a=ClassC.getT(6.02,5.3,19,21); //正确, Comparable a=ClassC.getT(6.02,5.3,19,21);//正确
该示例中,Interger、Double同时继承Number和Comparable,所以可以编译能够根据共同的超类型确定引入的操作类型)。
3.类型变量的限定
在使用泛型时,当我们需要限制该泛型类或者泛型方法的使用类型时(即仅仅让满足条件类型的对象能够使用该泛型,因为这样才能使用一些特有的方法等),就可以通过类型变量限定来操作。主要的声明方式如下:/* * 类型变量限定————类声明 * */ public class ClassE<T extends ClassA & InterfaceB,U extends ClassB> { //当继承多个类的时候用“&”来连接多个限定符,用“,”来分割多个类型变量(可能有人疑问InterfaceB换成ClassB可以不,在这里当然不可以,类是单继承,接口是多实现) @Override public String toString() { return "ClassE"; } /* * 类型变量限定————方法声明 * */ public <T extends ClassC> void method1(T... t){ return; } }
4.通配符类型
其实在谈到类型变量限定,就应该了解泛型中的通配符,因为其写法和类型变量限定十分相似,但又不同,这就涉及到关于继承的关系。在谈通配符类型之前先说泛型的继承关系。在引用1就说明了即使类之间存在继承关系,其对应泛型也非继承关系,在此以Java库中List,ArrayList和两个存在继承关系的类Father和Son说明泛型类之间的继承规则。在这里可以发现泛型类的继承和一般类的继承关系一样,不会因为类型变量的关系而发生改变,其实从类型擦除也可以理解,将泛型类ArrayList进行类型擦除后是同一个类,就不存在继承关系。但通配符不一样,可以达到这一效果。首先声明其定义如下:
public void method1(ClassA<? extends InterfaceA > tmp){ } public void method2(ClassA<?> classA){ } public ClassA<?> method3(){ return null; public void method4(ClassA<? super InterfaceA > tmp){ }
在此注意的是通配符类型“?”不是类型变量,因此不能定义泛型类和泛型接口,只能用作函数的返回值,而类型变量还能够对对象的实域类型进行声明。通配符不仅可以对下进行限定,也可以对上进行限定,甚至是不用限定,然而不管限定符怎样设置,其方法处理的都是ClassA对象,并且和类型变量限定不同的是,在类型变量限定中,是可以使用“&”对多个实现的接口或类进行处理,但是通配符限定只能对一个接口或类进行处理,因此以下几种方式声明都是错误的:
public class ClassF<? extends InterfaceA> {} //error public void method1(ClassA<? extends InterfaceA & InterfaceB > tmp){}//error
通过通配符类型,同一个泛型类可以处理具有继承关系类型的泛型类之间的转换,而直接通过类型变量限定会出错,如下:
ClassA<Father> father=new ClassA<>(); ClassA<Son> son=new ClassA<>(); son.setFiled1(new Son()); father=son;//error 此处的father和son没有任何关系,因此不能转换
正确的做法可以如下:
public static ClassA method(ClassA<? extends Father> s1){ ClassA<Father> classA= (ClassA<Father>) s1; //强制类型转换 //ClassA<? extends Father> classA=s1; return classA; } .... ClassA classA=ClassA.method(son);
通过这种方式就可以实现类型转换,其关系图如下:
因此从上述知识可以发现,通过合理的使用类型变量限定和通配符,可以很好的控制参数类型范围,如
class ClassF<T extends ClassA<? super ClassA>>。
Java泛型原理和注意事项
1.Java泛型类型擦除和重载
Java泛型是一种伪泛型,其编译后会进行类型擦除,替换为原生类型,并且在相应的地方进行强制类型转换,如ArrayList、ArrayList在运行时候都是同一个类。因此,在方法重载时,即使类型变量不一样,但如果特征签名(其参数列表及其对应的原生类型和方法名)一样,也会出现错误,例如下面的操作:public static void method(List<Integer> ints){ } public static void method(List<String> strs){ }
但是在SUN JDK1.6版本中,也可以根据方法的返回值类型来区别不同的方法
public static int method(List<Integer> ints){ //一般也编译不通过, return 1; } public static String method(List<String> strs){ return ""; }
在引用[3]的解释是说虽然方法重载的特征签名不包括方法的返回值类型,但是在Class文件格式中,只要描述符不是完全一致的两个方法就可以共存到一个Class文件中,在方法调用时,引人Signature来存储一个字节码层面的特征签名(代码层特征签名只包含方法名称、参数顺序和参数类型,而字节码的特征签名包含方法的返回值及受查异常表),因此可以根据类型变量调用对应的方法。然而这种情况在大多数编译器上是拒绝编译的。因此当需要使用多态时,可以考虑采用桥方法。
2.Java泛型数组创建
同时在创建泛型数组时,可以采用以下方式声明,不能创建参数化类型数组:ClassA<String>[] a= (ClassA<String>[]) new ClassA<?>[10]; ClassA<String>[] b=new ClassA[10]; //ClassA<String> c=new ClassA<String>[10]; //error
因为数组可以记住其元素类型,因此前两种方式应该是允许进行类型转换,将创建Class对象数组转换为具体的类型变量的泛型数组,而最后一种方式应该是不能允许的,这样可以以防出现类型转化错误。。
3.泛型类在静态上下文中的变量无效
不能在静态域和方法中引用类型变量,如下所示都是错误声明:public static T cc; //error public static T getFiled1(){ //error if(filed1==null){ return null; } return filed1; }
因为类中的静态方法和静态域是属于类而不属于对象的,是类的对象共有的,因此当泛型进行类型擦除之后,只剩下静态域和方法而没有声明其类型,因此导致程序无法运行。
4.不能抛出或捕获泛型类的实例
在泛型使用时,不能抛出和捕获泛型类异常,如下:try{ }catch (T e){ //error }
但是可以在异常规范中使用类型变量,如下方式就是合法的:
public <T extends Throwable> void method(T t) throws T{ try{ }catch (Throwable e){ throw t; } }
在这里编译会把这个异常当做未检查的异常进行处理。
引用:
【1】http://www.cnblogs.com/lwbqqyumidi/p/3837629.html
【2】java核心技术
【3】深入理解java虚拟机
相关文章推荐
- 一年去雾算法研究的总结。
- JAVA刷网站流量的技术研究总结
- Java泛型编程最全总结
- SNMP/SMI研究总结
- iOS新手开发总结
- Pedestrian Identification (2) ——研究现状总结
- Authorization and Profile Application Block 1.0研究总结
- 2013 Regional 总结 与 acm 感想 由新手到菜鸟
- 新手学QT之书籍总结篇
- 学习RAC小记-适合给新手看的RAC用法总结
- Mina2 研究总结
- Java泛型的全面总结
- Javascript研究心得总结
- [置顶] Spring框架研究总结之IOC
- 《计算机网络》知识总结-5.TCP的研究学习思路
- 研究领域总结(一):稀疏——字典学习
- 程序员面试、算法研究、编程艺术、红黑树、数据挖掘5大经典原创系列集锦与总结
- iOS GPUImage研究总结
- Java泛型四:Java泛型总结
- 六种PHP图片上传重命名方案研究与总结