`
fantaxy025025
  • 浏览: 1313993 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类

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语言程序设计 进阶篇(原书第8版).pdf

    《Java语言程序设计 进阶篇(原书第8版)》是一本深入探讨Java编程技术的权威书籍,旨在帮助读者提升对Java编程语言的掌握程度。这本书详细讲解了Java的高级特性和复杂概念,是Java程序员进阶的必备读物。 首先,书...

    Java语言程序设计进阶篇(第5版)源代码

    《Java语言程序设计进阶篇(第5版)》是一本深入探讨Java编程技术的权威教材,适合已经掌握Java基础的开发者进一步提升技能。这本书的源代码提供了丰富的实例,涵盖了多线程、网络编程、I/O流、集合框架、异常处理、...

    java语言程序设计 进阶篇 第十版 课后习题答案

    《Java语言程序设计 进阶篇 第十版》是一本深度探索Java编程的权威书籍,其课后习题是学习者巩固理论知识、提升实践能力的重要环节。本资源包含了书中多个章节的习题答案,包括chapter20至chapter34,涵盖了广泛的...

    Java语言程序设计进阶篇答案与代码

    Java语言程序设计进阶篇是Java开发者学习过程中不可或缺的一环,它涵盖了高级特性和实践技巧,旨在提升开发者对Java编程的深入理解。本资源包含了教材中的所有源代码和相关复习题、编程题的答案,这对于自学Java或者...

    Java语言程序设计 进阶篇 源代码与答案

    《Java语言程序设计 进阶篇》是一本深入探讨Java编程技术的专业教材,它涵盖了Java语言的高级特性和实际应用。这本书旨在帮助读者从基础知识过渡到更复杂的编程概念,提升编程技能,为解决实际问题打下坚实基础。...

    MyGeneric_sadb3t_carryfz8_泛型的使用进阶_

    本篇文章将深入探讨“泛型的使用进阶”,包括泛型缓存、泛型类、泛型方法和泛型委托的应用,以及它们在实际开发中的使用场景。 首先,泛型缓存是一种常见的优化技术,用于存储已经计算过的结果,避免重复计算。通过...

    Java语言程序设计.进阶篇(原书第8版)

    进阶篇(原书第8版)》是一本深入探讨Java编程技术的权威书籍,专为已经掌握了Java基础知识的开发者设计。这本书涵盖了Java编程的高级概念和实践技巧,旨在帮助读者提升Java开发技能,实现更高效、更安全、更可维护的...

    java语言程序设计进阶篇第十版PDF

    《Java语言程序设计进阶篇第十版》是一本深度探讨Java技术的专业书籍,旨在帮助已经具备Java基础知识的学习者进一步提升技能。这本书详细讲解了Java语言的高级特性、面向对象编程的精髓以及实际开发中的应用技巧。 ...

    Java语言程序设计.进阶篇.原书第10版

    在Java语言中,进阶篇通常涵盖以下关键知识点: 1. **多线程编程**:Java提供了内置的多线程支持,允许程序同时执行多个任务。书中会详细介绍Thread类和Runnable接口的使用,以及线程同步、互斥和死锁的概念,如...

    Java语言程序设计进阶篇源代码

    Java语言程序设计进阶篇是Java编程学习领域中备受推崇的一本教材,其第五版更是经典之作,深受程序员和学生们的喜爱。这份源代码包含了书中所讲述的各种Java编程概念和技巧的实际实现,对于深入理解Java语言及其应用...

    java语言程序设计 进阶版 第十版 复习题答案

    Java语言程序设计是编程领域中的基础且重要的课程,尤其对于进阶学习者而言,掌握其精髓至关重要。本书《Java语言程序设计 进阶版 第十版》提供的复习题及其答案,旨在帮助读者巩固并深化对Java语言的理解。下面将...

    Java语言程序设计进阶篇

    2. **异常处理**:Java的异常处理机制是其强项之一,进阶篇可能会涉及如何正确地捕获和处理异常,以增强程序的健壮性。 3. **集合框架**:包括List、Set、Map接口及其具体实现如ArrayList、LinkedList、HashSet、...

    2011.06 - Java语言程序设计-进阶篇(原书第8版

    《Java语言程序设计-进阶篇》作为一本专门针对有一定Java基础的读者的书籍,其内容会涉及以下几个方面: 1. 面向对象的高级特性:在进阶篇中,会深入探讨Java中面向对象的高级特性,比如抽象类、接口、内部类、匿名...

    Java语言程序设计-进阶篇(原书第10版 中英文)

    《Java语言程序设计-进阶篇》是Java编程领域的一本经典教材,其原书第10版提供了中英文双语版本,对于学习者来说,无论是深入理解Java语言的精髓,还是提升跨文化交流能力,都是不可多得的资源。本教材主要针对已经...

    Java语言程序设计(基础篇)(进阶篇)课后代码

    本资源包含“Java语言程序设计(基础篇)(进阶篇)”课程的课后代码,旨在帮助学习者深入理解和实践Java编程。 在基础篇中,学习者将接触到Java语言的核心概念,包括: 1. **Java简介**:了解Java的历史、特点...

    《Java语言程序设计(进阶篇)》 课后习题第25章代码chapter25.rar

    在本压缩包“chapter25.rar”中,包含的是《Java语言程序设计(进阶篇)》一书的第25章课后习题的源代码。这是一份宝贵的资源,对于正在学习Java编程,尤其是深入阶段的学生来说,是提高编程技能和理解Java高级特性...

    《Java语言程序设计(进阶篇)》 课后习题第23章代码chapter23.rar

    8. **泛型**:Java泛型增强了类型安全,减少了强制类型转换。习题可能包含泛型类、泛型接口、通配符以及泛型方法的使用。 9. **JVM内存管理**:理解Java虚拟机的内存模型对优化程序性能至关重要。可能讲解了堆内存...

    Java程序设计语言进阶篇(原书第8版)

    《Java程序设计语言进阶篇(原书第8版)》是Java开发者不可或缺的经典参考资料,其深入探讨了Java编程的高级特性和最佳实践。这本书详细介绍了如何利用Java的强大功能进行高效且可维护的编程,旨在帮助程序员提升...

Global site tag (gtag.js) - Google Analytics