java泛型一二

206 阅读4分钟

java泛型可以被写在方法上,接口上,类上面以及参数定义上。

为什么使用泛型中,oracle提到相比于没有泛型的代码,泛型提供了:

  • 编译检查
  • 消除强制转型
  • 提高代码可读性

本文试图从容器角度来分析泛型,理解泛型的好处以及泛型的表现。我们将list,map之类的可以存放数据的对象成为容器。在没有泛型出现之前,容器的类型是不可知的,只有开发者知道容器内可能放什么东西,但是java对此并没有限制,也即是开发者可以将任意的对象添加进容器内,反之从容器中取出的的元素也可以是任意对象。下面的情况很容易发生。

        List intList = new ArrayList();
        intList.add("element");
        Integer el = (Integer) intList.get(0);

将约束交给开发者的大脑,对于后续维护以及开发都是潜在的危险,因为后续可能无法正确使用对应的容器,更不用说取出元素的时候要添加强制转型来毁坏代码可读性。在容器中,泛型提醒人们容器在运行时装的是同一种类的对象。修改后的代码强壮性更高也更易读,包括约束也浮上纸面。

        List<Integer> intList = new ArrayList();
        intList.add(1);
        Integer el = intList.get(0);

有super和extend的泛型

在容器中,泛型使容器可以成为特定的容器,可以放置指定的类,比如List和List可以分别放置Number对象和Integer对象。但是这两类容器是无法互相赋值的。尽管Integer继承于Number,在java编译器看来这是两个不同的容器,假如想泛指多种容器,则需要使用super或extend来规定下限或上限。

上限-extend

以Number为例,List<? extend Number>泛指了List,List,List等所有继承了Number类的子类构成的容器,包括Number在内,其中Number是上限,再往上的类以及其他的类不被泛指。因此从List<? extend Number>中取出的一定是个Number对象,可以使用Number类有的方法。List,List,List定义的对象可以赋值给List<? extend Number>定义的对象,但是无法反过来赋值。

下限-super

以Integer为例,List<? super Number>泛指了List,List,List等Integer继承树上的类构成的容器,此时Integer是下限,但是假如Integer有子类,也会包含在里面。同样,List,List,List定义的对象可以赋值给List<? super Number>定义的对象,但是无法反过来赋值。

oracle官网给了一个图帮助我们理解,底下的类可以赋值给箭头指向的类。

例子

定义以下的内部类,其中Box类是容器,Fruit类是Food类的子类

    static class Box<T> {

        private T t;

        Box() {}
        Box(T t) {
            this.t = t;
        }

        T getT() {
            return t;
        }

        void setT(T t) {
            this.t = t;
        }
    }

    static class Food {}

    static class Fruit extends Food {}

定义三个变量

    Object object = new Object();
    Food food = new Food();
    Fruit fruit = new Fruit();

以下的代码illegal注释代表编译报错:

    Box<? extends Food> box1= new Box<>(food);
    box1.setT(food);  //  illegal (case1)
    box1.setT(fruit);  //  illegal (case2)
    box1.setT(object);  //  illegal (case3)
    food = box1.getT();
    fruit = box1.getT();  //  illegal (case4)

box1在运行时可能是一个food盒子,也可能是一个fruit盒子,这在编译期是无法确定的,因此所有的set操作(case1、2、3)都被禁止,理由是可能放置进去错误的类型。而get操作则因为extend的限制取出的一定是个food,但不一定是fruit(case4)

    Box<? super Food> box2 = new Box<>(food);
    box2.setT(food);
    box2.setT(fruit);
    box2.setT(object);  //  illegal (case5)
    food = box2.getT();  //  illegal (case6)
    fruit = box2.getT();  //  illegal (case7)

box2里面,错误的类型不被允许set进去(case5),但是下限类可以被set进去,上面的第二和第三行并没有报错,在super限制的继承树上,food和fruit肯定可以强转为继承树上的任意一个类。此处需要和box1做对比,case1中,food本身就是food,fruit也可以强转为food,为什么还被禁止set进去。因为food可以有多棵子树,假如存在子树meat,box1是放meat的,那么此时case2肯定是报错的,而同时,我们也无法得知case1里面的food是属于哪个类,其有可能是fruit强转的,对于泛型而言,不确定的类型肯定也会报错。

get操作因为无法super限制无法确切知道是哪个类而报错(case6,case7)

例外情况

由于需要兼容泛型出现前的情况,将一个不带泛型的容器赋值给一个带泛型的容器是被允许的,比如

        List list = new ArrayList();
        list.add("element");
        List<Integer> intList = list;

参考 : oracle泛型