##### 为什么需要泛型?或者说泛型是用来解决什么问题的?
* 单一类型容器需求 && 编译期安全检查 && 改善代码冗余
(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
通常来说,如果Apple是Fruit的子类型,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版)》是一本深入探讨Java编程技术的权威书籍,旨在帮助读者提升对Java编程语言的掌握程度。这本书详细讲解了Java的高级特性和复杂概念,是Java程序员进阶的必备读物。 首先,书...
《Java语言程序设计进阶篇(第5版)》是一本深入探讨Java编程技术的权威教材,适合已经掌握Java基础的开发者进一步提升技能。这本书的源代码提供了丰富的实例,涵盖了多线程、网络编程、I/O流、集合框架、异常处理、...
《Java语言程序设计 进阶篇 第十版》是一本深度探索Java编程的权威书籍,其课后习题是学习者巩固理论知识、提升实践能力的重要环节。本资源包含了书中多个章节的习题答案,包括chapter20至chapter34,涵盖了广泛的...
Java语言程序设计进阶篇是Java开发者学习过程中不可或缺的一环,它涵盖了高级特性和实践技巧,旨在提升开发者对Java编程的深入理解。本资源包含了教材中的所有源代码和相关复习题、编程题的答案,这对于自学Java或者...
《Java语言程序设计 进阶篇》是一本深入探讨Java编程技术的专业教材,它涵盖了Java语言的高级特性和实际应用。这本书旨在帮助读者从基础知识过渡到更复杂的编程概念,提升编程技能,为解决实际问题打下坚实基础。...
本篇文章将深入探讨“泛型的使用进阶”,包括泛型缓存、泛型类、泛型方法和泛型委托的应用,以及它们在实际开发中的使用场景。 首先,泛型缓存是一种常见的优化技术,用于存储已经计算过的结果,避免重复计算。通过...
进阶篇(原书第8版)》是一本深入探讨Java编程技术的权威书籍,专为已经掌握了Java基础知识的开发者设计。这本书涵盖了Java编程的高级概念和实践技巧,旨在帮助读者提升Java开发技能,实现更高效、更安全、更可维护的...
《Java语言程序设计进阶篇第十版》是一本深度探讨Java技术的专业书籍,旨在帮助已经具备Java基础知识的学习者进一步提升技能。这本书详细讲解了Java语言的高级特性、面向对象编程的精髓以及实际开发中的应用技巧。 ...
在Java语言中,进阶篇通常涵盖以下关键知识点: 1. **多线程编程**:Java提供了内置的多线程支持,允许程序同时执行多个任务。书中会详细介绍Thread类和Runnable接口的使用,以及线程同步、互斥和死锁的概念,如...
Java语言程序设计进阶篇是Java编程学习领域中备受推崇的一本教材,其第五版更是经典之作,深受程序员和学生们的喜爱。这份源代码包含了书中所讲述的各种Java编程概念和技巧的实际实现,对于深入理解Java语言及其应用...
Java语言程序设计是编程领域中的基础且重要的课程,尤其对于进阶学习者而言,掌握其精髓至关重要。本书《Java语言程序设计 进阶版 第十版》提供的复习题及其答案,旨在帮助读者巩固并深化对Java语言的理解。下面将...
2. **异常处理**:Java的异常处理机制是其强项之一,进阶篇可能会涉及如何正确地捕获和处理异常,以增强程序的健壮性。 3. **集合框架**:包括List、Set、Map接口及其具体实现如ArrayList、LinkedList、HashSet、...
《Java语言程序设计-进阶篇》作为一本专门针对有一定Java基础的读者的书籍,其内容会涉及以下几个方面: 1. 面向对象的高级特性:在进阶篇中,会深入探讨Java中面向对象的高级特性,比如抽象类、接口、内部类、匿名...
《Java语言程序设计-进阶篇》是Java编程领域的一本经典教材,其原书第10版提供了中英文双语版本,对于学习者来说,无论是深入理解Java语言的精髓,还是提升跨文化交流能力,都是不可多得的资源。本教材主要针对已经...
本资源包含“Java语言程序设计(基础篇)(进阶篇)”课程的课后代码,旨在帮助学习者深入理解和实践Java编程。 在基础篇中,学习者将接触到Java语言的核心概念,包括: 1. **Java简介**:了解Java的历史、特点...
在本压缩包“chapter25.rar”中,包含的是《Java语言程序设计(进阶篇)》一书的第25章课后习题的源代码。这是一份宝贵的资源,对于正在学习Java编程,尤其是深入阶段的学生来说,是提高编程技能和理解Java高级特性...
8. **泛型**:Java泛型增强了类型安全,减少了强制类型转换。习题可能包含泛型类、泛型接口、通配符以及泛型方法的使用。 9. **JVM内存管理**:理解Java虚拟机的内存模型对优化程序性能至关重要。可能讲解了堆内存...
《Java程序设计语言进阶篇(原书第8版)》是Java开发者不可或缺的经典参考资料,其深入探讨了Java编程的高级特性和最佳实践。这本书详细介绍了如何利用Java的强大功能进行高效且可维护的编程,旨在帮助程序员提升...