Java编程思想之泛型(下)

来源:互联网 发布:苹果5s能用4g网络吗 编辑:程序博客网 时间:2024/06/08 07:11

6 边界

边界使得你可以在用于泛型的参数类型上设置限制条件。你可以按照自己的边界类型来调用方法。因为擦除移除了类型信息,所以,可以用无界泛型参数调用的方法只是那些可以用Object调用的方法。但是,如果能够将这个参数限制为某个类型子集,那么你就可以用这些类型子集来调用方法。为了执行这种限制,Java泛型重用了extends关键字。extends关键字在泛型边界上下文环境中和在普通情况下所具有的意义是完全不同的。

interface HasColor{...}class Dimension{...}//this won't work -- class must be first,then interface://class ColoredDimension<T extends HasColor & Dimension>{...}class ColoredDimension<T extends Dimension & HasColor>{...}  //it's okinterface Weight{...}//as with inheritance , you can have only one concrete class but multiple interfaces:class Solid<T extends Dimension & HasColor & Weight>{...}

7 通配符

通配符被限制为单一边界。

//You can do this:List<? extends HasColor> list;//But you can't do this://List<? extends HasColor & Weigth> list;

下面展示了数组的一种特殊行为:可以向导出类型的数组赋予基类型的数组引用:

class Fruit{}class Apple extends Fruit{}class Jonathan extends Apple{}class Orange extends Fruit{}public class CovariantArrays{    public static void main(String[] args){        Fruit[] fruit = new Apple[10];        fruit[0] = new Apple(); //ok        fruit[1] = new Jonathan();  //ok        //Runtime type is Apple[],not Fruit[] or Orange[]:        try{            //Compiler allows you to add Fruit:            fruit[2] = new Fruit(); //ArrayStoreException        }catch(Exception e){            System.out.println(e);        }        try{            //Compiler allows you to add Orange:            fruit[3] = new Orange();    //ArrayStoreException        }catch(Exception e){            System.out.println(e);        }    }}/*运行结果为:java.lang.ArrayStoreException: Fruitjava.lang.ArrayStoreException: Orange*/

如果实际的数组类型是Apple[] ,你应该只能在其中放置Apple或Apple的子类型,这在编译期和运行时都可以工作。但是请注意,编译器允许你将Fruit放置到这个数组中,这对于编译器来说是有意义的,因为它有一个Fruit[] 引用——它有什么理由不允许将Fruit对象或者任何从Fruit继承出来的对象,放置到这个数组中呢?因此,在编译期,这是允许的。但是,运行时的数组机制知道它处理的是Apple[], 因此会在向数组中放置异构类型时抛出异常。

List<Fruit> flist = new ArrayList<Apple>(); //Compile Error: incompatible types
不能把一个涉及Apple的泛型赋给一个涉及Fruit的泛型。Applede List 不是 Fruit 的List。Apple的List将持有Apple和Apple的子类型,而Fruit的List将持有任何类型的Fruit。
与数组不同,泛型没有内建的协变类型。 因为数组在语言中是完全定义的,因此可以内建了编译期和运行时的检查,但是在使用泛型时,编译器和运行时系统都不知道你想用类型做些什么,以及采用什么样的规则。

但有时你想要在两个类型之间建立某种类型的向上转型关系,这正是通配符所允许的:

List<? extends Fruit> flist = new ArrayList<Apple>();   //任何拓展自Fruit对象//can't add any type of object: flist.add(new Apple());flist.add(new Fruit());flist.add(new Object());  //call that it's returns is safe: Fruit f = flist.get(0);

可以将其读作“具有任何从Fruit继承的类型的列表”。但是,这实际上并不意味着这个List将持有任何类型的Fruit。通配符引用的是明确的类型,因此它意味着“某种flist引用没有指定的具体类型”。如果你调用一个返回Fruit的方法,则是安全的,因为你知道在这个List中的任何对象至少具有Fruit类型,因此编译器允许这样做。

查看ArrayList的文档,可以发现,add() 将接受一个具有泛型参数类型的参数,因此,上述add()的参数就变成了“? extends Fruit”。从这个描述中,编译器并不能了解这里需要Fruit的哪个具体子类型,因此它不会接受任何类型的Fruit。编译器将直接拒绝对参数列表中涉及通配符的方法(列如add())的调用。这意味着将由泛型类的设计者来决定哪些调用是“安全的”,并使用Object类型作为其参数类型。为了在类型中使用了通配符的情况下禁止这类调用,我们需要在参数列表中使用类型参数。

超类型通配符可以声明通配符是由某个特定类的任何基类来界定的,方法是指定

...List<? super Apple> apples;  //从Apple导出的某种具体类型apples.add(new Apple());apples.add(new Jonathan());//apples.add(new Fruit()); //Error

参数Apple是Apple的某种基类型的List,这样你就知道向其中添加Apple或Apple的子类型是安全的。但是,既然Apple是下界,那么你可以知道向这样的List中添加Fruit是不安全的。

根据如何能够向一个泛型类型“写入”(传递给一个方法),以及如何能够从一个泛型类型中“读取”(从一个方法中返回),来着手思考子类型和超类型边界。

因此编译器只关注传递进来和要返回的对象类型。

无界通配符<?> - 实际上,它是在声明:“我是想用Java泛型来编写这段代码,我在这里并不是要用原生类型,但是在当前这种情况下,泛型参数可以持有任何类型。例如:List实际上表示”持有任何Object类型的原生List“,而List<?>表示”具有某种特定类型的非原生List,只是我们不知道那种类型是什么。“ 有一种情况需要使用<?>而不是原生类型。
...static <T> void f(Holder<T> holder){}static void f2(Holder<?> holder){    f1(holder); //Call with captured type}/*如果向一个使用<?>的方法传递原生类型,那么对编译器来说,可能会推断出实际的类型参数,使得这个方法可以回转并调用另一个使用这个确切类型的方法。*/...
原创粉丝点击