首页 > 代码库 > Java使用泛型的困顿

Java使用泛型的困顿

一、前言

  平时比较少接触泛型,之前也略微看过,自己也动手写过一些easy的demo,感觉也没啥难度,所以也就没怎么去深究,近来偶然的兴头去翻了翻相关的知识,突然发现,我勒个去,为嘛上面说的某些点我半天反应不过来?!毕业快三年了,这个盲点必须扫掉~~~本文不针对泛型做过多的举例,只是对于其中比较难以理解的部分做解读,不了解泛型的童鞋,自行百度~

 

二、正文

  先说说什么是泛型,泛型的定义:泛型是JDK 1.5的一项新特性,它的本质是参数化类型(Parameterized Type)的应用,也就是说所操作的数据类型被指定为一个参数,在用到的时候在指定具体的类型,这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口和泛型方法。(这边需要说明一点泛型方法和泛型类没有必然联系,普通类里面也可以有泛型方法)~

  从我的角度,曾经有困惑过我的问题有两个,其一:List<Father>和List<Son>之间的关系(Son是Father的子类);其二:为什么“List<? extends SomeClass> a”引用的对象只能使用get方法,不能add方法(编译报错,不过add(null)可以,后面会讲到),为什么“List<? superSomeClass> a”引用的对象只能使用add方法,不能使用get方法?

  先说说第一个问题,算了,感觉这样说不下去了,还是要介绍下“泛型”出现的背景,听说在JDK1.5之前,Java泛型程序设计是用继承来实现的,因为Object类是所用类的基类,所以只需要维持一个Object类型的引用即可,就比如ArrayList只维护一个Object引用的数组:

public class ArrayList//JDK1.5之前的
{
    public Object get(int i){......}
    public void add(Object o){......}
    ......
    private Object[] elementData;
}

这样会有两个问题:

1、没有错误检查,可以向数组列表中添加类的对象

2、在取元素的时候,需要进行强制类型转换

这样,很容易发生错误,比如:

/**jdk1.5之前的写法,容易出问题*/
ArrayList arrayList1=new ArrayList();
arrayList1.add(1);
arrayList1.add(1L);
arrayList1.add("asa");
int i=(Integer) arrayList1.get(1);//因为不知道取出来的值的类型,类型转换的时候容易出错        

  所以,在JDK1.5之后,加入了泛型来解决类似的问题,例如在ArrayList中使用泛型:

 /** jdk1.5之后加入泛型*/
 ArrayList<String> arrayList2=new ArrayList<String>();  //限定数组列表中的类型
 //      arrayList2.add(1); //因为限定了类型,所以不能添加整形
 //      arrayList2.add(1L);//因为限定了类型,所以不能添加整长形
 arrayList2.add("asa");//只能添加字符串
 String str=arrayList2.get(0);//因为知道取出来的值的类型,所以不需要进行强制类型转换

  还要明白的是,泛型特性是向前兼容的。尽管 JDK 5.0 的标准类库中的许多类,比如集合框架,都已经泛型化了,但是使用集合类(比如 HashMap 和 ArrayList)的现有代码可以继续不加修改地在 JDK 1.5 中工作。

针对问题一解答:

  Father是Son的父类,那么List<Father>会是List<Son>的父类吗?答案:不是!对于初学者来说,这个结论感觉就是辣眼睛~要理解这个问题,就先需要理解泛型出现的背景,很大程度上是为了避免强制类型转换导致的不兼容问题。(我这边是使用List这个类来做说明,其实完全可以建一个泛型类Tes<T>,然后以Test<Father>和Test<Son>来举例,初学者不必纠结于此,只是List是大家熟知,所以以此举例)~我们知道List里面有两个方法一个是get,一个是add,这两个方法的声明如下形式:

public abstract interface List<E> extends Collection<E>
{
    ....................
    public abstract E get(int paramInt);
    public abstract boolean add(E paramE);
    ....................
}

  我们可以举个实际的例子来说明下,假设List<Number>是List<Integer>和List<Float>的父类:

import java.util.ArrayList;
import java.util.List;


public class TestMain {
    public static void main(String[]args) throws Exception{
        List<Integer> intList = new ArrayList<Integer>();
        intList.add(10);
        List<Number> numberList = intList;//此处编译报错//numberList.get(0); ------1
    }
}

  那么在“1”处,得到的结果是Number还是Integer呢?由于在编程过程中的顺序不可控性,导致在必要的时候必须要进行类型判断,且进行强制类型转换,显然,这与泛型的理念矛盾,因此,SomeClass<Father>不能视为SomeClass<Son>的父类(特别说明:Fahter<T>是Son<T>的父类,这个和现在的结论不矛盾)~

针对问题二的解答:

  看下下面这段代码(摘录别人的,详见“参考链接”部分),注释里面说的已经非常清楚,最核心的一句话就是为了确保类型能够兼容

  先看看这行代码:List<? extends Pet> list= new ArrayList<Dog>(),很多人可能会问,我这边不是已经指定泛型的具体类型为Dog吗?但是从编译器的反馈来看,只知道左边方括号内的内容“? extends Pet”,所以编译器只知道是某种Pet的子类,但是并不知道具体的子类是什么,无法确保在运行时类型兼容(这个我是这样理解的,在运行时,必定会根据右边的表达式知道泛型的实际类型为Dog,如果在代码里面使用add(new DogOther()),DogOther也是Pet的一个子类,这边add的动作相当于将DogOther类型的对象实例,赋值给Dog类型的引用,DogOther和Dog并不兼容,那么运行时必定报错,所以不允许),所以不允许使用add函数~那为什么又允许get呢?首先我们要明白,既然此处不能add,那么如果list有值必定是之前就有一个有确定值的List对象实例假设是list01,这个list01的泛型必定是确定的,然后list01赋值给list,在使用list.get的时候返回的值应该是一个确定类型的对象,所以可以使用get~

  再来看看List<? super Dog> list2 = new ArrayList<Dog>(),同样,在编译期间,编译器并不知道泛型的具体类型是什么,只是知道是Dog类型或者其超类,如果在运行时,可以知道泛型的类型为Dog,在调用list2.add(new PettyDog())的时候,相当于将PettyDog的对象实例,赋值给Dog,根据里氏替换原则,这样是允许的,所以这边使用add是合法的~那为什么此处调用get报错呢?这是因为add方法可以往list2里面设置很多Dog或者Dog类型的子对象,那么在调用get的时候返回的到底是什么呢?因为有多种可能,又要进行类型判断,且进行强制类型转换,显然,这与泛型的理念又发生矛盾~

public class TestMain {
    public static void main(String[] args) {
        Item<Dog> item = new Item<Dog>(new Dog());
        item.get();
        Item<? extends Pet> item2 = new Item<Dog>(new Dog());
        item2.get().out();
        /**知道某种限制,但是不知道具体是什么**/
        List<? extends Pet> list = new ArrayList<Dog>();
        /**
         * The method add(capture#2-of ? extends Pet) in the type List<capture#2-of ? extends Pet>
         *  is not applicable for the arguments (Dog)
         * list.add(new Dog());
         * 从错误代码得知,编译期list的类型是List<? extends Pet>,所以通配符还是个未知类型;
         * 只知道是某种Pet的子类,但是并不知道具体的子类是什么,无法确保类型兼容;
         * 即使list.add(new Object()); (Object类型与Pet的子类不兼容) 也是不可以的;
         * list.add(null);这个可以;所以对于使用通配符的泛型,只能“取”不能“加”;
         * 编译器只允许加null;因为只有 Pet的子类 = null;等式成立;
         */
        //list.get(0);
        List<? super Dog> list2 = new ArrayList<Dog>();
        /**
         * 这关系到泛型的边界(bounds),list通配符使用的是上边界,无法确定具体的类;
         * 这里list2用的是下边界,取放的是Dog某种超类,但是并不知道具体的超类是什么;
         * 我们只知道Dog的子类可以跟Dog超类类型兼容
         * 所以这种形式的通配符只能“存”不能“取”
         */
        list2.add(new Dog());
        /**
         * list2.add(new Object());
         */
        list2.add(new PettyDog());
        /**ClassCastException**/
        //list2.add((Dog) new Run());
    }
}
interface Pet {
    public void out();
}
class Item<T extends Pet> {
    T item;
    public Item(T item) {
        this.item = item;
    }
    public T get() {
        return item;
    }
    public void put(T item) {
    }
}
class Run {
    
}
class Dog extends Run implements Pet {
    @Override
    public void out() {
        System.out.println("The Dog!");
    }
    public void getMsg(Item<?> item) {
        item.get();
        /**
         *The method put(capture#3-of ?) in the type Item<capture#3-of ?>
         * is not applicable for the arguments (Item<capture#4-of ?>)
         * capture:占位符被称为这个特殊通配符的捕获
         * item.put(item);
         */
    }
}
class PettyDog extends Dog {
}

 

三、参考链接

http://www.cnblogs.com/ljxe/p/5521840.html

http://blog.csdn.net/lonelyroamer/article/details/7864531

https://my.oschina.net/u/782865/blog/198906

 

Java使用泛型的困顿