Java泛型总结(2)进阶篇

 

##### 为什么需要泛型?或者说泛型是用来解决什么问题的?

  * 单一类型容器需求 && 编译期安全检查 && 改善代码冗余

    (1)拿容器来说,实际中的需求,大部分容纳的都是同一个类型的,而不是通用类型的。如果需要通用类型的,那就直接使用List<Object>就可以了。

    (2)既然需要单一类型容器,就需要编译器检查,否则放入了不合适的类型,只能在运行期出错了

    (3)既然是单一类型容器,就希望取出来的时候不用再进行显性的类型转换了

看看实际的例子,体现了上面的3点:

List myIntList=new LinkedList(); //1
myIntList.add(newInteger(0)); //2
Integer x=(Integer)myIntList.iterator().next(); //3

List<Integer> myIntList=newLinkedList<Integer>(); //1’

myIntList.add(newInteger(0)); //2’

Integerx=myIntList.iterator().next(); //3’

在第1行代码中指定List中存储的对象类型为Integer,这样在获取列表中的对象时,不必强制转换类型了。

 

  * 通用程序需求(类似c++模版一样)

设计底层结构或者通用容器的时候,需要一定的通用性,类似c++模版一样。

否则就得用抽象接口,但实际中怎么能麻烦的去要求不可控的实际情况都实现某些接口呢?

 

看下面的代码,不可能期望容器接纳的类都去实现某个接口。(继承就更是不能奢望了)

class Box<T> {

    private T data;

    public Box() {

    }

    public Box(T data) {
        this.data = data;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}

 

明白设计的目标,是理解的重要部分,否则后面会出现很多疑问。

 

##### Java设计泛型来达到上面的目标,但又有哪些限制和需要注意的地方呢?

 

//基类
class Plant {}
class Fruit extends Plant{}
class Apple extends Fruit{}
class RedApple extends Apple{}
class Orange extends Fruit{}
 

 

1. 分清主要矛盾和次要矛盾:容器是主,容器的泛型类型的关系为辅。

List<Fruit> fruitList = new ArrayList<Fruit>();
List<Apple> appleList = new ArrayList<Apple>();

fruitList和appleList是不同的容器对象,即两个盛不同东西的容器。类型都是List,没有继承关系。

//fruitList = appleList;//compile error: Required FruitList but Found AppleList

 通常来说,如果AppleFruit的子类型,G是一种带泛型的类型,则G<Fruit>不是G<Apple>的子类型。这也许是泛型学习里面最让人容易混淆的一点。

 

 

2. 通配符<?>,通用程序设计 

//使用通配符?,表示可以接收任何元素类型的集合作为参数
void printCollection3(Collection<?> c) {
    for (Object e:c) {
        System.out.println(e);
    }
}

这样就实现了不同泛型限制的容器,可以更通用的调用。

 

3. 通配符<?>的缺陷:使用了泛型限制的容器,只能是一种类型确切的类型,不能杂乱添加,否则就违背了泛型设计的初衷了。

static void generalInsertAndGet() {
    List<?> c = new ArrayList<String>();
    //c.add(new Object()); //compile time error,不管加入什么对象都出错,
    //除了null外,这是非常严重的!!!。
c.add(null); //OK
    //String s = c.get(0);//compile time error,凡是通配符的地方,都以向上边界得到对象,
    //也只能这样,否则编译器能怎样呢,他也不知道确切的类型。
}

如果试图往使用通配符?的集合中加入对象,就会在编译时出现错误。需要注意的是,这里不管加入什么类型的对象都会出错。这是因为通配符?表示该集合存储的元素类型未知,可以是任何类型。往集合中加入元素需要是一个未知元素类型的子类型,正因为该集合存储的元素类型未知,所以我们没法向该集合中添加任何元素。唯一的例外是null,因为null是所有类型的子类型,所以尽管元素类型不知道,但是null一定是它的子类型。 

另一方面,我们可以从List<?> lists中获取对象,虽然不知道List中存储的是什么类型,但是可以肯定的是存储的类型一定是Object的子类型,所以可以用Object类型来获取值。如for(Object obj: lists),这是合法的。

 

4. 边界通配符的上限:<? extends XXXClass>,取值的上边界为XXXClass,但没法向容器add值。

表示这样的容器:泛型类型为XXXClass或其任一的子类。比如参数为List<? extends Fruit>,表示可以接收这样的参数传递进来调用:泛型为Fruit或任一Fruit子类的容器。

static void generalCall(List<? extends Fruit> list) {
    Fruit fruit = list.get(0);
    //Apple apple = list.get(0);//compile error,这是合理的,因为这里通配符的上边界
    //就是Fruit,编译器只能知道这些,并不能知道到底是边界的哪个子类。
    //list.add(new Object());//compile error,这是合理的,因为是个上届为Fruit的容器,
    //不能添加其他的东西
    //list.add(new Fruit());//compile error,这看着有点儿不合理的!!!
    //这是最容易出现问题的地方!!!一个Fruit的容器居然不能添加Fruit!!!
    /**
     *     真实情况是任何时候,都只能调用一种泛型限制了的容器参数,
     * 参数可以是new ArrayList<Fruit>(),new ArrayList<Apple>(),new ArrayList<RedApple>()
     * 但到底是哪一个,运行的时候是不知道的。所以这里的<? extends Fruit>泛型和<?>泛型
     * 在添加上没有区别,都不能添加任何的东西,null除外。
     *     那问题就来了,这个扩展了上限限制的<? extends Fruit>有什么好处呢?
     * 好处是可以直接得到泛型上限的值,而不用使用转型。如下代码所示。
     * 这总比让容器泛型的边界直接抵达顶层类Object要好很多,对程序来说,
     * 也更精确了一些,这就是改进了的地方。
     */
Fruit fruit1 = list.get(10000);

    //list.add(new Apple());//compile error,如果明白了上面的理由,边界为Fruit的容器参数,
    //连Fruit的都不能添加,Apple就更不能添加了。
    /**
     * 要知道,真实情况的调用下,传递给框架的容器参数,是单一类型的容器且不能
     * 确定是Fruit的哪个类型。所以这里不能确定添加泛型边界的哪个子类。
     */
}

public static void main(String[] args) {
    generalCall(new ArrayList<Fruit>());
    generalCall(new ArrayList<Apple>());
    generalCall(new ArrayList<RedApple>());
    //generalCall(new ArrayList<Plant>());//compile error
}

 

5. 边界通配符下限:<? super XXXClass>,可以向容器add泛型下边界的值,但取值的上边界是Object。

表示:此容器的泛型为XXXClass或此容器的泛型为XXXClass的任一父类。

这总比单一的<?>多了一些约束,这就够了,很不错了。

static void generalCall2(List<? super Fruit> list){
    //Fruit fruit = list.get(10000);//compile error. Required Fruit Found Object.
    //如果明白了extends那里的关键点,这里就很容易明白了。
    //关键点:1.运行时某个时刻调用的容器参数的泛型一定是固定的一种,而不是多种。 
    //2. 想想编译器是否可以知道这一点,若不知道或不能确定,那不让编译通过。
    //这里不能通过的关键地方在于super限制了参数容器的下限边界为Fruit,
    //这就是说泛型为Fruit是可以传递进来的,Fruit的子类也是Fruit,所以也可以添加进来。
    //但是某个时刻,从参数容器中取值值,编译器就不能确定取出的值的类型 是边界之上的哪一种
    //了。这种情况下,不能让编译通过。
    //Apple apple = list.get(10000);//compile error.
}
static void call101(){
    generalCall2(new ArrayList<Fruit>());
    generalCall2(new ArrayList<Plant>());
    generalCall2(new ArrayList<Object>());
}

public static void main(String[] args) {
    call100();call101();
}

边界通配符总结:

如果你想从一个数据类型里获取数据,使用 ? extends 通配符

如果你想把对象写入一个数据结构里,使用 ? super 通配符

如果你既想存,又想取,那就别用通配符。

注意:边界通配符的上述限制,仅仅是因为其要作为参数传递。否则不存在上述问题。请想想为什么~

既然是作为参数传递过来,那么如果必须做相应的操作,就copy一份,就可绕过编译不能通过的问题。

 

6. 关于泛型数组:数组没有必要使用泛型,因为数组本身就是强类型的,不存在容器的两个问题。

规则:不能创建一个确切泛型类型的数组,只能创建带通配符的泛型数组。

不能创建一个确切泛型类型的数组。如下面代码会出错。

List<String>[] lsa = new ArrayList<String>[10]; //compile error.

 

       因为如果可以这样,那么考虑如下代码,会导致运行时错误。

//List<String>[] lsa = new ArrayList<String>[10]; // 实际上并不允许这样创建数组,因为是精确类型的泛型数组
Object o = lsa;
Object[] oa = (Object[]) o;
List<Integer>li = new ArrayList<Integer>();
li.add(new Integer(3));
oa[1] = li;// unsound, but passes run time store check
String s = lsa[1].get(0); //run-time error - ClassCastException

因此只能创建带通配符的泛型数组,如下面例子所示,这回可以通过编译,但是在倒数第二行代码中必须显式的转型才行,即便如此,最后还是会抛出类型转换异常,因为存储在lsa中的是List<Integer>类型的对象,而不是List<String>类型。最后一行代码是正确的,类型匹配,不会抛出异常。

List<?>[] lsa = new List<?>[10]; // ok, array of unbounded wildcard type
Object o = lsa;
Object[] oa = (Object[]) o;
List<Integer>li = new ArrayList<Integer>();
li.add(new Integer(3));
oa[1] = li; //correct
String s = (String) lsa[1].get(0);// run time error, but cast is explicit
Integer it = (Integer)lsa[1].get(0); // OK

 

Prefer:

http://qiemengdao.iteye.com/blog/1525624 本篇文章总结的非常好!向作者表示感谢(文中有错误,看的时候注意)。

http://www.cnblogs.com/lwbqqyumidi/p/3837629.html

http://blog.csdn.net/daniel_h1986/article/details/5708605

http://developer.51cto.com/art/200909/153983.htm

http://www.liutime.com/java_circlescontentinfo/id=488

http://blog.csdn.net/jiafu1115/article/details/6624254  #很不错的总结

 

0

0

0

8

0

0

0

 

你可能感兴趣的:(java泛型)