由上面的图可以非常清楚的看到,Java中的容器的继承结构
在顶层有很多接口,这些接口声明了很多的基本的抽象方法,之后的许多类按照不同的方式实现这些接口,
同时可能在增加一些自己的方法,从而形成了不同功能的容器,比如:ArrayList类与LinkedList类都继承了List接口,
但是他们在实现List的接口时,方法体并不一样,这样一来就形成了不一样的容器,这些容器都位于java.util包中
接下来我们将一一介绍这些容器的功能,当然我们不可能将容器的所有的知识全部不漏的讲解,我们只是将一些
思想讲解一下,使我们对容器有一个整体的认识,至于一些具体的方法如何使用,需要在用时查询官方文档
首先我们先对List、Set、Queue、Map接口的功能做一个介绍:
①List类型的容器实际上就相当于一个长度可变的
“对象数组”,和向量的概念是相同的
②Set类型的容器实际上相当于一个“对象集合”
这样的容器中是不允许加入重复的元素的,并且
对于其中的元素并没有一种排序的概念,和集合
是一个概念
③Queue类型的容器实际上相当于一个“对象队列”
④Map类型的容器中的每个元素包含一对儿键对象和
值对象,即在关键字对象和值对象之间产生一种映射关系,通过键对象类查找值对象
接下来,我们介绍逐一介绍这些具体的容器都有什么功能
实例:
Collection c = new ArrayList
(Array.asList("A","B","C"));
一、Collection 接口
这个接口只一个抽象度非常高的接口,这其中声明了那些实现了他的容器类都具有的特性方法,也就是诸如:
ArrayList类、LinkedList类、HashSet类等其中的继承自Collection接口的方法的实现方式都是一样的,
比如:boolean add(E e) 这个方法在所有的这些类中都是将一个元素加入容器当中;
即Collection中的抽象方法都是从“一堆元素”的概念上抽象出来的Collection接口的常用抽象方法有:
① public Boolean add(E e)
向容器当中添加一个元素
②boolean addAll(Collection extends E> c)
向当前的容器中加入指定的Collection类型的容器的所有元素
③void clear()
移除容器当中的所有的元素
④boolean contains(Object obj)
判定当前的容器中是否有指定的元素,我们想肯定调用了equals()方法了
⑤boolean containsAll(Collection> c)
判定当前容器中是否包含了,指定的容器中的所有的元素
⑥boolean isEmpty()
判定当前的容器是否为空
⑦Iterator iterator()
返回当前容器对应的迭代器
⑧boolean remove(Object obj)
如果容器当中含有该元素,则将之删除
⑨boolean retainAll(Collection> C)
将当前的容器中的元素和指定的容器元素取交集存入当前容器中
10.int size ()
返回容器的元素数目
11.Object[] toArray()
根据当前容器中的元素创建一个对象数组
二、实用类Collections
在上面的图中的右下角有两个实用类:Collections类
和Arrays类,Arrays类中的静态方法主要是操作数组的,而Collection类中也有很多静态的方法是用来操作List、Set、Queue、Map类型的容器的,
这两个实用类都位于java.util包中
在Arrays类中也有一个操纵容器的方法
static List
asList(T... a)
返回有可变参数列表创建的List类型的容器对象,并返回容器的引用,如:
String [] strArray = {“123”,”wr”,”fsdf”};
List ls = Array.asList(strArray);
在Collection类中的静态方法,能够实现容器中元素的替换、删除、排序、整个复制等等,具体的就不在多讲
三、List(列表)
像数组一样,List也能建立数字索引与对象的关联,表达的是数据结构中的线性表的概念,List接口的常用实现类是:ArrayList和LinkedList
①ArrayList类
就像名子显示的那样,ArrayList是线性表中的数组,也就是说这个类中的数据域实际上就是一个对象数组,
因此,他有数组的一切的特性,还有同时封装在一起的方法
因此对于查找来说,速度是非常的快的,但是删除与插入操作就相对较慢,而且ArrayList是线程不安全的
②LinkedList类
就像名字显示的那样,LinkedList是线性表当中的链表,也就是说这个类中的数据域实际上就是一个对象链表,
还有同时封装在一起的方法,值的注意的是内部的这个链表是一个双向的循环链表,
也就是说,插入语删除操作是非常快的,但是查找较慢
另外需要非常注意的是,LinkedList类还继承了Queue接口,
LinkedList类中单独还有addFirst()、addLast()、getFirst()、getLast()、removeFirst()、removeLast()等方法,
这就是的LinkedList类的对象能够用作队列和栈来使用,同样,LinkedList是线程不安全的
四、Set(集合)
1、Set 中不接受任何重复的元素,如果试图将重复的对象加到同一个Set对象当中的话,是不能加入的;所谓的相同的对象,
是根据equals(参数)方法来判定的,这就要涉及到该方法的重写问题了,这在之前已经讨论过了
2、一般Set最常用于判定一个指定元素的归属性问题,即判定一个元素是否已经在在当前的集合当中了!可见查找
是Set型容器的最常用的、最重要的方法了;Set与Collection具有完全一样的接口,实际上Set就是Collection,只是应用的行为不同罢了!
3、虽然Set有HashSet、TreeSet、LinkedSet三种实现类,但是在《thinking in java》 中明确指出,
对于查找而言HashSet类失效率最高的一个类,所以在没有特殊的要求时,使用HashSet是最好的选择
1、这个类的内部有一个HasMap实例对象作为其成员域,
这说明在HashSet类的实例对象中的元素都是按照哈希表的形式存储在HashMap对象中了,这也就说明了,HashSet类的查找效率是非常高的
2、通过add(参数)向HashSet对象当中,添加元素时,首先会调用hasCode()方法,计算出该元素的存放位置,
比较该位置的元素是否与要添加的元素相同,所以又要调用equals()方法;同时在查找时也要调用hashCode方法;
这就要求我们将equals()方法和hashCode方法同时重写,保证两个方法相匹配,即保证:
当两个元素对象“相等时”,那么两个元素对象应该生成相同的散列码
这就用到了另一份文档“Java中的hashCode()方法的深入剖析.doc”(其中那个例子非常有代表性)
我们在获得这个容器中的每个元素时,可以使用简化的for语句实现,这是允许的,可以查阅一下简化的for语句的使用前提
五、Queue(队列)
也就是说,Queue类型的容器专门用来用作容器来使用的
具有队列的特性
①利用LinkedList类型的容器实现普通的队列、双向队列、栈等数据结构(LinkedList类同时实现了Queue接口),
我们可以通过将LinkedList的对象向上转型到Queue,这样一来的话就只能使用继承自Queue接口的专门用于队列的方法,
使LinkedList对象看起来像一个完完全全的队列容器;对于双向队列,java中有一个专门用于实现双端队列的接口Deque,位于java.uitl包中,
而且LinkedList类也实现了该接口,所以同样可以将LinkedList对象向上转型形成一个单纯的双端队列
②PriorityQueue(优先级队列)
所谓的优先级队列就是在该队列的内部有一个堆的数据结构,实现了每次出对的元素,都是优先级最高的元素;
但是程序是如何知道每个元素的优先级的呢,这里有两种方式:自然方式和人为指定方式
所谓的自然方式是指,如果容器中的元素对象是String、Integer、或者是Character类型的内嵌数据类型的话,
那么这种优先级是程序已经自行规定好的了,即“较小”的拥有较高的优先级;但是对于实际问题中的很多的元素都不是内嵌的数据类型,
那么就要人为的指定优先级的设定规则,解决这一问题的方法就是,使元素类实现Comparable
接口,这个接口中只有一个方法
public int compareTo(Object obj)
只要在元素类中将该方法实现的话,那么程序就会自行的根据这个方法来设定优先级,如:
注意:对于PriorityQueue来说,他的遍历器不能保证按照优先级次序便利所有的元素
六、Map(映射)
Map(映射)是一种将键对象和值对象进行映射的集合,需要注意的是如果值对象是Map类型的话那么,就形成了多级映射;
在实现该接口的类中效率最高的一个就是HashMap类,一般在没有特殊的要求的情况下最好及使用这个类型的容器,
这个容器的用法和前面讲过的Hashtable容器非常相近,可以参照该部分的内容进行理解,
其中最终要的一点是对于hashCode()和euqals()方法的重写;
另外需要注意的一点是:如果在哈希表的应用中,迭代性能比查找性能更加站上风,那么在设定初始容量时,
就应该尽量的不让初始容量太高,而同时让装填因子设定的得尽量的高;如果是说实际使用哈希表的时候,
有非常多的键值对要加入到该容器中的话,使用足够大的初始容量创建该容器,将使得映射关系更加有效的存贮,
因为如果初始容量设定的过于小的话,那么容器就会按照规则不断的进行扩充,那么就会耗费非常多的时间