• 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>的区别

  • 相关阅读:
    Linux命令之find exec rm-删除所有日志文件
    Linux常用系统信息查询命令
    shell后台运行脚本
    Linux 下后台运行程序
    Ubuntu配置和修改IP地址
    ssh免密码登录配置方法
    (转)linux如何将新硬盘挂载到home目录下
    Shell中if [ ] 中-a到-z的意思
    安防监控相关
    本地yum源的建立
  • 原文地址:https://www.cnblogs.com/stevenczp/p/7990267.html
Copyright © 2020-2023  润新知