java泛型详解

1. extends与super

extends关键字限制了泛型类可以使用的泛型参数类型的上限(Upper Bound)

super关键字限制了泛型类可以使用的泛型参数类型的下限(Lower Bound)

先定义几个简单的Class

    class Food {
    }

    class Fruit extends Food {
    }

    class Meat extends Food {
    }

    class Apple extends Fruit {
    }

    class Orange extends Fruit {
    }

    class Pork extends Meat {

    }

    class Beef extends Meat {

    }

extends解析

直接赋值的限制

以泛型类List为例,如果我们声明下面这样的一个List

        List<? extends Fruit> fruitList;

那么后续给这个fruitList赋值时,这个值允许使用的泛型参数的上限就是Fruit,也就是Fruit或者Fruit的子类。所以下面的代码是可以编译通过的

        fruitList = new ArrayList<Apple>();
        fruitList = new ArrayList<Orange>();    

下面的代码则会引发编译失败

        fruitList = new ArrayList<Food>();//Food不是Fruit的子类,编译失败

泛型方法参数的限制

在上面的例子中,fruitList的含有泛型参数的方法,例如add,是受到极大的限制的:

        fruitList.add(new Object());//编译错误
        fruitList.add(new Food());//编译错误
        fruitList.add(new Fruit());//编译错误
        fruitList.add(new Apple());//编译错误
        fruitList.add(null);//编译通过

可以发现,除了不带有Class类型信息的null,任何Class(甚至包括Object)的实例,都不能作为add方法的泛型参数,这是为什么呢?

因为extends关键字只是限制了泛型类可以使用的泛型参数的上限,编译器并不知道fruitList实际指向的实例使用的泛型参数具体是什么,可能是Fruit,也可能是Apple或者Orange。

这句话不太好理解,但是如果把fruitList直接替换成已经正确设置了泛型参数的泛型类的实例,然后调用泛型方法,可能会比较好懂。

例如在泛型参数为Orange时,上面的代码可以转换为下面的形式:

        new ArrayList<Orange>().add(new Object());//编译错误
        new ArrayList<Orange>().add(new Food());//编译错误
        new ArrayList<Orange>().add(new Fruit());//编译错误
        new ArrayList<Orange>().add(new Apple());//编译错误
        new ArrayList<Orange>().add(null);//编译通过

也就是说,编译器采用了非常保守的策略来确保类型安全(只允许发生隐式的从子类向父类的类型转换)。

泛型方法返回值的限制

在上面的例子中,由于可以确定fruitList对应的实例使用的泛型参数必然是Fruit或者Fruit的子类,所以如果有方法的返回值是泛型参数,那么可以确定这个返回值必然也是Fruit或者Fruit的子类了

        Fruit fruit = fruitList.get(0);

上面这行代码是IDEA的自动补全返回值类型的效果。

super解析

直接赋值的限制

以泛型类List为例,如果我们声明下面这样的一个List

        List<? super Fruit> fruitList;

那么后续给这个fruitList赋值时,这个值允许使用的泛型参数的下限就是Fruit,也就是Fruit或者Fruit的父类。所以下面的代码是可以编译通过的

        fruitList = new ArrayList<Fruit>();//编译通过
        fruitList = new ArrayList<Food>();//编译通过

而下面的代码则会引发编译失败

        fruitList = new ArrayList<Apple>();//编译错误

泛型方法参数的限制

在上面的例子中,fruitList的含有泛型参数的方法,例如add,也受到了一定的限制:

        fruitList.add(new Object());//编译错误
        fruitList.add(new Food());//编译错误
        fruitList.add(new Fruit());//编译通过
        fruitList.add(new Apple());//编译通过
        fruitList.add(null);//编译通过

可以发现,只允许使用Fruit或者Fruit的子类的实例作为参数,这又是为啥呢?

因为super关键字是限制了泛型类可以使用的泛型参数的下限,泛型参数无论是什么,都必然是Fruit或者Fruit的父类,这必然也是Fruit的子类的父类了。

所以只要给泛型方法传入Fruit或者Fruit子类的实例作为参数,最多只会引发隐式类型转换,不会导致异常。

以泛型参数为Food为例,把上面的代码转换一下:

        new ArrayList<Food>().add(new Object());//编译错误
        new ArrayList<Food>().add(new Food());//编译通过,但是如果换另外一种父类就出错了
        new ArrayList<Food>().add(new Fruit());//编译通过
        new ArrayList<Food>().add(new Apple());//编译通过
        new ArrayList<Food>().add(null);//编译通过

这也是编译器为了确保类型安全作出的努力

泛型方法返回值的限制

在上面的例子中,由于无法确定fruitList对应的实例使用的泛型参数到底是什么玩意(可能是Fruit,也可能是Food,甚至可能是Object),所以只能用万能的Object作为返回值类型了:

        Object object = fruitList.get(0);

上面这行代码是IDEA的自动补全返回值类型的效果。

PECS法则

Producer->extends    Consumer->super

如果你希望某个泛型类只能生产(方法有泛型返回值)受限定的元素(Producer),那么就该用extends关键字来做限定

如果你希望某个泛型类只能消费(方法有泛型参数)受限定的元素(Consumer),那么就该用super关键字来做限定

小结

extends和super的限定初看非常难懂,但是如果把泛型类的泛型参数试着实例化并且展开一下,就好理解多了。

这些奇怪的限制,主要是为了在任何情况下都能保证类型安全(只发生隐式的子类向父类的转换)

 

参考资料

Java泛型中<? extends E>和<? super E>的区别

posted @ 2017-12-06 00:39  qeDVuHG  阅读(325)  评论(0编辑  收藏  举报