《第12章 泛型与容器类》

  • Post author:
  • Post category:其他




12.1 泛型

泛型的实质就是将数据类型参数化,通过为类,接口及方法设置类型参数来定义泛型。

泛型使一个类或一个方法可在多种不同类型的对象上进行操作,使用泛型意味着编写的代码可以被很多类型不同的对象所重用,从而减少数据类型转换潜在的错误。



12.1.1 泛型的概念

  1. 泛型实际上是在定义类,接口或方法时通过为其增加“类型参数”来实现的。即泛型所操作的数据类型被指定为一个参数,这个参数被称为类型参数,所以说,泛型的实质是将数据的类型参数化。
  2. 当这种类型参数用在类,接口,以及方法的声明中时,则分别称为泛型类,泛型接口和泛型方法。
  3. 基本格式:

    1. 泛型类的定义:修饰符 class 类名
    2. 泛型接口的定义:public interface 接口名
    3. 泛型方法的定义:public static 返回值类型 方法名(T 参数)
  4. 定义泛型之后,就可以在代码中使用类型参数T来表示某一种数据的类型而非数据的值,即T可以看作泛型的一种“类型形式参数”。在定义类型参数后,就可以在类体或接口中定一的各个部分直接使用这些类型参数。
  5. 而在使用这些具有泛型特性的类或接口时,需要指明实际的具体类型,即用“类型实际参数”来替换“类型形式参数”,也就是说,用泛型类创建的对象就是在类体内的每个类型参数T处分别使用这个具体的实际类型替代。
  6. 泛型的世家参数必须是类类型,利用泛型类创建的对象称为泛型对象,这个过程也称为泛型实例化
  7. 因此,泛型的概念实际上时基于“类型也可以先变量一样实现参数化”这一简单的设计理念实现的,因此泛型也称为参数多态。



12.1.2 泛型类及其应用

  1. 在使用泛型定义的类创建对象时,即在泛型实例化时,可以根据不同的需求给出类型参数T的具体类型。
  2. 而在调用泛型类的方法传递或返回数据类型时可以不用进行类型转换,而是直接使用T作为类型来替代参数类型或返回值类型。

    • **说明:**在实例化泛型类的过程中,实际类型必须时应用类型,即必须时类类型,不能用int,double,或char等这样的基本类型来替换类型参数T
  3. T表示任意一种数据类型,并可用该类型来声明类成员变量,成员方法的参数或返回值
  4. 类型参数由实际对象的类型决定,因此凡是在了体现处T的地方均被换成String。

    • **说明:**当一个泛型有多个类u新参数时,每个类型参数在该泛型中都应该时唯一的,如:不能定义Map<K,K>形式的泛型,但是可以定义Map<K,V>形式的泛型。



12.1.3 泛型方法

  1. 一个方法是否是泛型方法与其所在的类是否是泛型类没有关系。要定义泛型方法,只需要将泛型的类型参数设置于返回值类型前面即可,在Java中任何放啊包括静态方法和构造方法都可以声明为泛型方法,泛型方法除了定义不同,调用时和普通方法一样。
  2. 一般来说,编写Java泛型方法时,返回值类型和至少一个参数类型应该时泛型,而且类型应该时一致的,如果只有返回值类型或惨啊书类型之一使用了泛型,这个泛型方法的使用就大大的受限制,基本限制到与不使用泛型一样的程度

    1. **注意:**若泛型方法的多个形式参数使用了相同的类型参数,并且对应的多个实参就有不同的类型,则编译器会将该类型参数指定为这多个实参所具有的“最近”共同父类直至Object
    2. **说明:**一个static放啊,无法访问泛型类的泛型参数,所以如果static方法需要使用泛型能力,必须使其成为泛型方法。
  3. 当使用泛型类时,必须在创建泛型对象的时候指定类型参数的实际值,而调用泛型方法时,通常不必知名参数的类型,因为编译器有个功能叫做:类型参数推断,此时编译器会为我们找出具体的类型。类型推断只对赋值操作有效,其他时候并不起作用。
  4. 设计泛型方法的目的主要是针对具有容器类型参数的方法的,如果编写的代码并不接受和处理容器类型,就根本不需要使用泛型放啊
  5. 泛型方法与泛型类之间的一个重要差别就是,对于泛型方法,不需要把司机的类型传递给泛型方法;但是泛型类却恰恰相反,即必须把实际的类类型参数传递给泛型类。



12.1.4 限制泛型的可用类型

  1. 在定义泛型类时,默认可以使用任何类型来实例化一个泛型类都一项,当在Java语言中,也可以在泛型类创建对象时对数据类型做出限制,语法格式为:

    • class ClassName
    • 该语句表示T时ClassName类的类型参数,且T有一个限制,即T必须是anyClass类及其子类或是实现了anyClass接口的类。且无论anyClass是类或接口,在进行泛型限制时都必须使用extends关键字
    • **注意:**对于实现了某接口的有限制泛型,也就是extends关键字,而不是implements关键字
    • 在定义泛型类时,若没有使用extends关键字限制泛型的类型参数时,默认时Object欸下的所有子类,即和是等价的
  2. 在利用泛型进行实例化时,若放心的十进参数的类之间有父子关系时,参数化后得到的泛型类之间并不会具有同样的父子关系。即子类泛型“并不是一种”父类泛型。



12.1.5 泛型的类型通配符和泛型数组的应用

  1. 泛型通配符的主要作用:

    1. 用于创建可重新赋值但不可修改其内容的泛型对象、
    2. 是用在方法的参数中,限制传入不想要的类型实参
  2. 当需要在一个程序中使用同一个对象名区应用不同的对象时,这是就需要使用通配符“?”创建泛型类对象。
  3. 但是条件是被创建的这些不同泛型对象的类型实参必须是某个类或是继承该类的子类或是实现某个接口的类。
  4. 也就是说,只知道通配符“?”表示是某个类或继承该类的子类或是实现某个接口的类,但是具体是什么类型不知道。

    1. 如:泛型类名<? extends T> o = null;//声明泛型类对象o
    2. 其中“?extends T”表示是T或T的未知子类型或是实现接口T的类
    3. 所以在创建泛型对象o时,若给出的类型参数不是类T或T的子类或是实现接口T的类,则编译报错
  5. 通配符“?”除了在创建泛型类对象时限制泛型类的类型之外,还可以将由通配符限制的泛型类对象用在方法的参数中防止传入不允许接收的类型参数。
  6. 在创建泛型类对象时,如果只是用了“?”通配符,则默认时“? extends Object”,所以”?“也被称为非受限通配。
  7. 对于一个泛型类来说,在创建相应的泛型类对象时,类型参数T除了使用某个实际类型替换外,话可以用通配符”?”,但是这两者的用法时不一样的。
  8. 直接使用通配符<?>创建泛型对象,有两个 特点:

    1. 具有通用性,即该反省类的其他对象可以赋值给通配符”?“创建的泛型对象,因为”?“等价于”? extends Object“,反之不可。
    2. 用通配符”?“创建的泛型对象,只能获取或删除其中的信息,当不可为其添加新的信息。
  9. 在泛型通配符”? extends T”中,由于T被认为时类型参数”?“的上限,所以”? extends T”也被称为上限通配符,
  10. 当然也可以对类型参数进行下限限制,此时只需将extends改为super即可,所以”?super”
  11. 引入通配符的主要目的是,支持泛型中的子类,从而实现多态。如果泛型方法的目只是为了能够适用于多种不同类型或支持多态,则应选用通配符。
  12. 泛型方法中类型参数的优势是可以表达多个参数之间或参数与返回值之间的类型依赖关系,如果方法中并不存在类型之间的依赖关系,则可以不适用泛型方法,而选用通配符。一般的,由于通配符更加清晰,简明,因此在程序开发过程中,建议尽量采用通配符。



12.1.6 继承泛型类与实现泛型接口

  1. 被定义为泛型的类或接口可被继承与实现
  2. 在定义泛型接口时,泛型接口也可被实现

    1. 如:interface in{}
    2. class SubClass<T1,T2>implements in{}



12.2 容器类

  1. 容器类就Java以类库的形式共用户开发程序时直接使用的各种数据结构,所谓数据结构,就是以某种方式将数据组织在一起,并存储在计算机中。
  2. 数据结构不仅可以存储数据,还支持访问和处理数据的操作。在面向对象的思想中,一种数据结构被认为时一个容器。
  3. 数组是一中简单的数据结构,除数组外Java还以类库的形式提供了许多其他数据结构,这些数据结构通常成为给容器或称集合类。



12.2.1 Java容器框架

  1. Java容器框架中由两个名称分别为Collection和Set的接口,为了防止名称的冲突,本书将Colllection译为容器,而将Set作为集合。
  2. Java容器框架提供了一些线程的数据结构可以使用,这些数据结构三个hi可与i存储对象的集合,在这里都西昂也称为元素。
  3. 从JDK5开始,容器框架全部采用泛型实现,且都存放在java.util包中,
  4. Java容器框架结构由两颗接口树构成,第一颗树根节点为Collection接口,他定义了所有容器的基本操作,如增删改查。
  5. 它的子接口Set,List等则提供了更加特殊的功能,其中Set的对象用于存储一组不重复的元素集合,而Llist的对象用于存储一个由元素构成的线性表。
  6. 第二棵树根结点为Map接口,他保持了“键”到“值”的映射,可以通过键实现对值得快速访问。



12.2.2 Collection接口

  1. Collection接口通常不能直接使用,当该接口提供了添加元素,删除元素,管理元素得方法。
  2. 由于Set接口和List他接口都继承了Collection接口,因此这些方法对结合Set与列表List是通用的。
  3. java.lang.Object

    1. java.lang.Iterable可迭代接口

      1. Collection容器接口

        1. List列表接口

          1. LinkedList链表类
          2. ArrayList数组列表类
        2. Set集合接口

          1. HashSet哈希集合类<–LinkedHashSet链表哈希集合类
          2. SortedSet有序集合接口<–TreeSst树集合类
    2. Map映射接口

      1. HashMap哈希映射类
      2. SortedMap有序映射接口<–TreeMap树映射类
    3. Iterator迭代器接口<–ListIterator列表迭代器接口



12.2.3 列表接口List

  1. 列表接口List是Collection子接口,他是一种包含有序元素的线性表,其中的元素必须按照顺序存放,且可重复,也可以是空值null。
  2. 元素之间的顺序关系可以由添加到列表的先后来决定,也可由元素值得带线啊哦来决定。
  3. List接口使用下标来访问元素。下标的范围为0~size()-1
  4. List接口新增了许多方法,使之能在俩表中根据具体得未知添加和删除元素
  5. 实现List接口得类主要有两个:链表类LinkedList和数组列表类ArrayList,他们都是线性表。
  6. LinkedList链表采用链表结构保存对象,使用寻辉煌双链表实现List,这种结构向链表中任意位置插入,删除元素时,不需要移动其他元素,链表得大小是可以动态增大或减小得,但不具有随机存取特性。
  7. ArrayList数组列表中使用一维数组实现List,该类实现的是可变数组,允许所有元素包括null。具有随机存取特性,插入,删除元素是需要移动其他元素,氮元素很多时,插入,删除操作的速度很慢。
  8. 在向ArrayList中添加元素时,其内容会自动增大,但不能自动缩小,但可以使用trimToSize()方法将数组的容量减小到数组列表的大小。
  9. 如何选择这两中国线性表,通常的原则是:

    1. 若要通过下标随机访问元素,但除了在末尾处之外,不再其他位置插入或删除元素,则应该选择Array List类
    2. 但若需要在线性表的任意位置进行插入或删除操作,则应该选择inkedLList类。



12.2.4 集合接口Set

  1. Set时一个不含重复元素的集合接口,它继承自Collection接口,并没有声明其他方方法,它的方法都是从Collection接口继承来的。
  2. Set集合中的对象不按特定的方式排序,只是简单的把对象加入集合中即可,但加入的对象一定不能重复。
  3. 集合中元素的顺序与元素加入集合的顺序无关。
  4. 实现Set接口的两个主要类是哈希集合HashSet及树集合TreeSet



1.哈希集合HashSet

  1. 哈希集合对所包含的元素的访问并不是像线性表一样使用下标,而是根据哈希码来存取集合中的元素。
  2. 哈希集合是在元素的存储位置和元素的值K之间建立一个特定的对应关系F,使每个元素与一个唯一的存储位置相对应。
  3. 在查找时,只要根据元素的值K,计算F(K)的值即可,如果此元素在集合中,则必定在存储位置F(K)上,因此不需要与集合中的其他元素进行比较便可直接取得所查的元素。
  4. 称这个对应关系f为哈希函数,按这种关系建立的表称为哈希表也称为散列表。
  5. HashSet类时基于哈希表的Set接口实现,HashSet根据哈希码来确定元素在集合中的存储位置(即内存地址),因此可以根据哈希码来快速的找到集合中的元素。
  6. HashSet类不保证迭代顺序,且允许元素值为null.在比较两个加入哈希集合HashSet中的元素是否相同时,会先比较哈希码方法HashCode()的返回值是否是相同,若相同,则在使用equals()方法比较其存储位置,
  7. 若两者都相同,则视为相同的元素。
  8. 之所以比较了哈希码之后,还要通过equals()方法进行比较,是因为对不同元素计算出的哈希码可能相同。
  9. 因此,对于哈希集合来说,若重写了元素对应的equals()方法或hashCode()方法中的某一个,则必须重写另一个,以保证其判断的一致性。



2.树集合TreeSet

  1. 树集合TreeSet类不仅实现了Set接口,还实现了java.util.SortedSet接口。TreeSet的工作原理与HashSet相似,但TreeSet增加了一个额外的步骤,以保证集合中的元素总是处于有序状态。
  2. 因此,但排序很重要时,就选择了TreeSet,否则应选择HashSet。TreeSet类的大多数方法继承自其父类或祖先类。



12.2.5 映射接口Map

  1. Map是另一种存储数据结构的对象,Map接口与List接口和Set接口有明显的区别。
  2. Map中的元素都是程队出现的,它提供了键到值的映射。
  3. 值是要存入Map中的元素,在将元素存入Map对象时,需要同时给定一个键,键决定了元素在Map中的存储位置。一个键和它对应的值构成一个条目,真正在Map中存储的时这个条目。
  4. 键很像下标,但是在List中下标时整数,而在Map中键可以是任意类型对象。
  5. 如果要在Map中检索一个元素,必须提供相应的键,这样就可以通过键访问到其对应元素的值。
  6. Map中的每个键都是唯一的,且每个键最多只能映射到一个值。由于Map中存储元素的形式较为特殊,所以Map没有继承Collection接口。
  7. 映射接口Map常用的实现类有HashMap和TreeMap。HashMap类是基于哈希表的Map接口实现的,所以HashMap通过哈希码对其内部的映射关系进行快速查找,因此对应于添加和删除银色和关系效率较高,并且允许使用nul值和null键,但必须保证键的唯一性
  8. 而类TreeMap中的映射关系存在一定的顺序,如果希望Map映射中的元素也存在一定的顺序,应该使用TreeMap类实现Map映射,由于TreeMap类实现的是Map映射中的映射关系是根据键值对象按照一定的顺序排列的,因此不允许键对象为null



本章小结

  1. 在定义类,接口或方法时若指定了“类型参数”,则分别称为泛型类,泛型接口和泛型方法
  2. 用泛型类创建的泛型对象就是在泛型类体内的每个类型参数T处分别用某个具体对的实际类型替代,这个过程就称为泛型实例化,利用泛型类型创建的对象称为泛型对象
  3. 在创建泛型对象的过程中,实际类型必须时应用类型,而不能用基本类型
  4. 泛型方法与其所在的类是否是泛型没有关系
  5. 在调用泛型方法时,可以将实际类型放在尖括号内作为方法名的前缀
  6. Java泛型方法的返回值类型和至少一个参数类型应该时泛型,而且类型应该是一致的。Java泛型方法广泛应用在方法返回值类型和参数均是容器类对象
  7. 虽然泛型的类型参数代表一种数据类型,但不能使用泛型的类型参数创建对象
  8. 在泛型中可以用类型参数声明一个数组,但不能使用类型参数创建数组对象
  9. 不能再静态环境中使用泛型类的类型参数
  10. 异常类不能时泛型的,即在异常类中不能使用泛型的类型参数
  11. 在定义泛型类或是使用泛型类创建对象时,对放心的类型做出限制称为泛型限制。
  12. 泛型类的通配符有三种形式:

    1. 第一种是“?”,它等价于“? extends Object”称为非受限通配符
    2. 第二钟是”?extends T”表示T或T的一个未知子类型,称为上限通配符
    3. 第三种是:“?super T”,表示T或T的一个位置父类型,被称为下限通配
  13. 当方法中的多个参数之间或参数返回值之间粗壮乃这依赖关系时,则应选用泛型方法。如果方法中不存在放心之间的依赖关系,则应选用通配符
  14. 容器是存储对象的数据结构的集合。容器框架中定义的所有接口和类都存储在java.util包中
  15. 容器的当前元素获取其后续元素进行访问的过程称为迭代,迭代也称为遍历
  16. List的对象用于存储一个有元素构成的线性表,Set的对象是存储一组不重复的元素集合,Map的对象保持了键到值的映射
  17. List是一种包含有序元素的线性表,其中的元素必须按照顺序存放,且可重复,也可以是null,实现List接口的链表类LinkedList和数组列表LinkedList
  18. LinkedList是实现List接口的链表类,采用双向链表结构保存元素,访问元素的时间取决于元素在表中所处的位置,当 链表的增长或缩小则任何的开销
  19. ArrayList是实现List接口的数组列表类,它使用一维数组实现List,来支持元素的快速访问,当在数组的扩展或缩小时则需要额外的系统开销
  20. Set时一个不含重复元素的即可接口,实现Set接口的两个主要类是哈希集合HashSet及树集合TreeSet
  21. HashSet工作原理是在哈希集合中元素的”值“与该元素的存储位置之间建立起一种映射关系,这种映射关系称为哈希函数或散列函数,有哈希函数计算出来的数值称为哈希码或散列索引吗。虽然HashSet中的元素是无需的,当由于HashSet特性还是可以快速的访问或添加元素
  22. 因为对不同元素计算出的哈希码可能相同,所以判断哈希集合中的元素是否相同需要同时使用hashCode()方法和equals()方法
  23. TreeSet类对象中的元素总是有序的,所以当插入元素时需要一定的开销
  24. Map中的元素都是成对出现的,它提供了键到值得映射
  25. 映射接口Map常用的实现类有HashMaphe TreeMap,HashMap类与TreeMap类的关系如同HashSet与TreeSet的关系一样
  26. HashMap是基于哈希表的Map接口实现的,允许使用null值和null键,但必须保证键的唯一性,hashMap是无序的
  27. TreeMap类中的映射关系存在一定的顺序,不允许建对象是null,TreeMap是有序的



常考面试题

  1. 什么是泛型的参数类型?泛型的主要优势是什么?在什么情况下使用泛型方法?泛型类与放心方法的主要区别是什么?

    1. 泛型的实质是指参数化类型的能力
    2. 泛型是在定义类,接口或方法时通过为其增加”类型参数“来实现的。即泛型所操作的数据类型被指定为一个参数,这个参数被称为类型参数
    3. 使用泛型的主要优势就是能够在编译时而不是在运行时检出错误,
    4. 设计泛型方法的目的主要针对具有容器类型参数的方法,如果编写的代码并不接受和处理容器类型,就根本不需要使用泛型方法
    5. 泛型方法与泛型类之间的一个重要差别就是,对于泛型方法,不需要把实际的类型传递给泛型方法,当泛型类却相反,即必须把实际的类型参数传递给泛型类
  2. 在泛型中,类型通配符的主要作用时什么?

    1. 类型通配符”?“的主要作用有两个方面,

      1. 一是用于创建可重新赋值当不可修改其内容的泛型对象
      2. 二是用在方法的参数中,限制传入不想要的参数类型实参
  3. 分别讲述LinkedLList与ArrayList,HashSet与TreeSet,HashMap与TreeMap有何异同?

    1. LinkedList与ArrayList都是线性表,且元素可以重复,也可以是空值null
    2. LinkedList链表类你工作链表结构 对象,使用循环双链表实现List。这种结构在向链表中任意位置插入,删除元素时不需要移动其他元素,链表的大小可以动态增大或减小,但不具有随机存取的特性。ArrayList数组列表类使用唯一数组实现List,该类实现的是可变数组,允许所有元素,包括null;具有随机存取特性,插入,删除元素时需要移动其他元素,当元素很多时,插入,删除操作的速度较慢。
    3. HashSet与TreeSet中的元素不能重复。HashSet根据哈希码来确定元素在集合中的存储位置,HashSet类不保证迭代顺序,且允许元素值为Nulll.TreeeSet中的元素总是处于有序状态。
    4. HashMap和TreeMap中的元素提供来了键到值的映射,键决定了元素的存储位置,一个键和它对应的值构成一个条目,即”键-值“对,真正存储的是这个条目
    5. HashMap类是基于哈希表的Map接口的实现,因此在对于添加和删除映射关系效率较高,并且允许使用null值和null键,当必须保证键的唯一性
    6. 而类TreeMap中的映射关系存在一定的顺序,由于TreeMap类实现的Map集合中的映射关系是根据键对象按照一定的顺序排列的,因此不允许键对象是null