Core Java (十九) 泛型程序设计(Generic parogramming)

来源:互联网 发布:云计算的含义 编辑:程序博客网 时间:2024/06/05 19:03

泛型程序设计(Generic parogramming)意味着编写的代码可以被很多不同类型的对象所重用。

泛型,提供了类型参数(type parameters),使得程序具有更好的可读性和安全性。

例如:

    ArrayList<String> files = new ArrayList<String>();

表明了ArrayList类有一个类型参数String来指示元素的类型。


泛型类

泛型类的简单实例:

    Pair class Pair<T, U>{......}

泛型类的定义:一个泛型类就是具有一个或多个类型变量的类。如上述泛型类Pair具有两个类型变量(T和U)。

类型变量:用大写的T(或U,S)表示普通类型变量,E表示集合的元素类型,K和V分别表示表的关键字与值的类型。

实例化泛型类型:用具体的类型替换类型变量就可以实例化泛型类型。


泛型方法

泛型方法可以定义在普通类中,也可以定义在泛型类中。

例如:

    public static <T> T getMiddle(T[] a){        return a[a.length / 2];    }
类型变量<T>,要放在修饰符的后面,返回类型的前面。

调用泛型方法,在方法名前的尖括号中放入具体的类型。

    String[] names = {"john", "Bob", "Lily"};    String middle = ArrayAlg.<String>getMiddle(names);

但是,大多数情况在,我们可以省略类型参数,直接调用方法。

    String[] names = {"john", "Bob", "Lily"};    String middle = ArrayAlg.getMiddle(names);


类型变量的限定

有时候,我们必须要求某个类型变量必须实现某个接口再能让程序正确执行,比如 public static <T> T min(T[] a){ ... }方法,很容易知道T必须是实现了Comparale接口才能得到最小值。我们只需要:

    public static <T extends Comparable> T min(T[] a){ ... }
这表示,min方法只能被实现了Comparable接口的类的数组调用。

<T extends BoundingType>,T是绑定类型(BoundingType)的子类型,BoundingType可以是类或者接口。当BoundingType有多个时,用&分隔,且限定中至多有一个类,如果有类,则类必须是限定列表中的第一个。


泛型代码和虚拟机

虚拟机没有泛型类型对象,所有对象都属于普通类。无论何时定义一个泛型类型,都自动提供一个相应的原始类型(raw type)。

原始类型用第一个限定的类型变量来替换,如果没有限定就用Object来替换。


翻译泛型表达式:

当调用泛型方法是,如果擦除返回类型,编译器会插入强制类型转换

例如:

    Pair<Employee> buddies = ......    Employee buddy = buddies.getFirst();
getFirst方法在泛型中返回一个Employee类型的对象,但是类型擦除后,返回一个Object类型的对象,这时,编译器会自动插入(Employee)buddies.getFirst(),强制类型转换。


翻译泛型方法:

public static <T extends Comparable> T min(T[] a)   ---> public static Comparable min(Comparable[] a)


合成的桥方法:

这是为了解决类型擦除导致的多态性丧失而创造的方法。

小例子:

擦除类型前:

泛型类Pair:

 public class Pair<T>{        ........        public void setSecond(T second){......}    }

一个普通的类:

    class DateInterval extends Pair<Date>{        public void setSecond(Date second){...}    }
擦除类型后

Pair类变成了:

 public class Pair{        ........        public void setSecond(Object second){......}    }

这个DateInterval类变成了:

class DateInterval extends Pair{        public void setSecond(Date second){...};}

可以看出,两个setSecond方法的参数完全不同,他们是两个完全不同的方法,如果运行如下语句:

    DateInterval interval = new DateInterval(...);    Pair<Date> pair = interval;    pair.setSecond(aDate);
那么,最后一句,pair.setSecond(aDate)是调用的那个方法呢?下面来分析一下:

setSecond(Date second)方法仅在DateInterval中有效,setSecond(Objiec second)在Pair及其子类中都有效。这里已经擦除了类型,所以pair.setSecond(aDate)中的pair为Pair类的对象,必将调用Pair类中的那个setSecond(Object second)方法。这样,setSecond本应具有的多态性消失了。注意:DateInterval类并没有重写setSecond方法

所以,要利用合成的桥方法重新找回多态性。

合成的桥方法只需要在DateInterval类中加一个setSecond(Object second)方法:

class DateInterval extends Pair{        public void setSecond(Date second){...};        public void setSecond(Object second){        setSecond((Date)second);        }            }

就这样,setSecond(Object second)重写了超类中的同签名方法,实现了多态性。


约束与局限性

  1. 约束与局限性大多是由类型擦除引起的。
  2. 不能用基本类型实例化类型参数。由于primitive type不是Object的子类。
  3. 运行时类型查询只适用于原始类型(raw type)。a instanceof Pair<T>与a instanceof Pair<String>有相同结果,T被忽略了。
  4. 不能抛出也不能捕获泛型类实例。
  5. 参数化类型的数组不合法。Pair<String>[]  myTest = new Pair<String>[10]; //错误
  6. 不能实例化类型变量。new T(...);//错误
  7. 泛型类的静态上下文中类型变量无效。
  8. 注意擦除后的冲突。

通配符(wildcard type)

Pair<? extends Employee>子类型限定的通配符

表示任何泛型Pair类型,它的类型参数是Employee的子类。

Pair<? extends Employee>是Pair<Employee>和Pair<Manager>的超类,而Pair<Employee>和Pair<Manager>没有任何关系。

  • ? extends Employee getFirst();
  • void setFirst(? extends Employee);

Pair<? extends Employee>中某个访问器getFirst()方法的返回类型是? extends Employee,它赋给一个Employee类型的对象是完全合法的。而setFirst方法,编译器只知道参数类型是Employee类或其子类,具体不知道,所以编译器拒绝传递任何特定的类型。


Pair<? super Employee>超类型限定的通配符

表示任何泛型Pair类型,它的类型参数是Employee的超类。

Pair<? super Employee>是Pair<Employee>和Pair<Object>的超类。

  • ? super Employee getFirst();
  • void setFirst(? super Employee);
Pair<? super Employee>中某个访问器getFirst()方法的返回类型是? super Employee,即Employee类型或其超类,只能把它赋给一个Object。
而更改器方法void setFirst(? super Employee),编译器虽然不知道其参数确切的类型,但是可以通过Employee类及其子类对象调用它(其实就是调用超类的方法)。


综上,带有超类型限定的通配符可以向泛型对象写入,带有子类型限定的通配符可以从泛型对象读取。

PECS法则:
producer-extends,consumer-super。如果参数化类型表示一个生产者,就使用<? extends T>,如果他表示消费者,就使用<? super T>。


Pair<?>无限定通配符
  • ?  getFirst();
  • void setFirst(? );
getFirst方法仅能赋给Object类型的对象。setFirst方法不能被调用。



原创粉丝点击