您的位置:首页 > 编程语言 > Java开发

java泛型——新手研究和总结

2017-08-21 12:13 369 查看
学习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 泛型