集合类简介
Java的集合类主要由两个接口派生而出:Collection和Map,这两个接口又包含了一些接口或实现类。
一个Collection代表一组Object,即Collection的元素(Elements)。一些Collection允许相同的元素而另一些不行。一些能排序而另一些不行。Java SDK不提供Collection的直接实现,提供的类都是继承自Collection的“子接口”如List和Set。所以Set、List和Map可以看做集合的三大部分。
Collections是一个集合框架的帮助类,里面包含一些对集合的排序,搜索以及序列化的操作。
集合框架的优点如下:
(1)使用核心集合类降低开发成本,而非实现我们自己的集合类。
(2)随着使用经过严格测试的集合框架类,代码质量会得到提高。
(3)通过使用JDK附带的集合类,可以降低代码维护成本。
(4)复用性和可操作性。
使用泛型的好处?
(1)泛型允许我们为集合提供一个可以容纳的对象类型,因此,如果你添加其它类型的任何元素,它会在编译时报错。这避免了在运行时出现ClassCastException,因为你将会在编译时得到报错信息。
(2)泛型也使得代码整洁,我们不需要使用显式转换和instanceOf操作符。
(3)它也给运行时带来好处,因为不会产生类型检查的字节码指令。
遍历集合的方法:Iterator接口和foreach循环
Collection有一个重要的方法:iterator(),返回一个迭代器Iterator,用于遍历集合的所有元素。Iterator模式可以把访问逻辑从不同的集合类中抽象出来,从而避免向客户端暴露集合的内部结构。
每一种集合类返回的Iterator具体类型可能不同,但它们都实现了Iterator接口,因此,我们不需要关心到底是哪种Iterator,它只需要获得这个Iterator接口即可,这就是接口的好处,面向对象的威力。
迭代器取代了Java集合框架中的Enumeration(遗留类),Iterator更加安全,因为当一个集合正在被遍历的时候,它会阻止其它线程去修改集合(以抛异常的方式)。要确保遍历过程顺利完成,必须保证遍历过程中不更改集合的内容(Iterator的remove()方法除外),所以,确保遍历可靠的原则是:只在一个线程中使用这个集合,或者在多线程中对遍历代码进行同步。
Iterator接口提供的三种方法:
(1)boolean hasNext():返回集合里的下一个元素。
(2)Object next():返回集合里下一个元素。
(3)void remove();删除集合里上一次next方法返回的元素。
典型的用法如下:
public class TestIterator {
public static void main(String[] args) {
//创建一个集合
Collection books = new HashSet();
books.add("轻量级J2EE企业应用实战");
books.add("Struts2权威指南");
books.add("基于J2EE的Ajax宝典");
//获取books集合对应的迭代器
Iterator<String> it = books.iterator();
while(it.hasNext()) {
String book = it.next();
System.out.println(book);
if (book.equals("Struts2权威指南")) {
it.remove();
//使用Iterator迭代过程中,不可修改集合元素,下面代码引发异常
//books.remove(book);
}
//对book变量赋值,不会改变集合元素本身
book = "测试字符串";
}
System.out.println(books);
}
}
程序运行结果:
Struts2权威指南
基于J2EE的Ajax宝典
轻量级J2EE企业应用实战
[基于J2EE的Ajax宝典, 轻量级J2EE企业应用实战]
说明:
(1)通过语句book = "测试字符串";
对迭代变量book进行赋值时,当我们再次输出books集合时,集合里的元素没有任何变化。即当使用Iterator对集合元素进行迭代时,Iterator并不是把集合元素本身传给迭代变量,而是把集合元素的值传给了迭代变量。因为java都是值传递,详见:java参数传递的方式。
(2)当使用Iterator来访问Collection集合元素时,只有通过Iterator的remove方法才能删除当前元素。其它在遍历过程中更改集合内容的行为都会引发java.util.ConcurrentModificationExcption异常。
对比一下,for each
的方式线程不安全,但简化了代码,也不用管是否越界。
List接口
List是有序的Collection,可以有相同的元素,使用此接口能够精确的控制每个元素插入的位置。用户能够使用索引(元素在List中的位置,类似于数组下标)来访问List中的元素,类似于Java的数组。
除了具有Collection接口必备的iterator()方法外,List还提供一个listIterator()方法,返回一个ListIterator接口,和标准的Iterator接口相比,ListIterator多了一些add()之类的方法,允许添加,删除,设定元素,还能向前或向后遍历previous()。
实现List接口的常用类有LinkedList,ArrayList,Vector和Stack(后两个是遗留类)。
LinkedList类
LinkedList实现了List接口,允许null元素。此外LinkedList提供额外的get,remove,insert方法在LinkedList的首部或尾部。这些操作使LinkedList可被用作堆栈(stack),队列(queue)或双向队列(deque)。擅长频繁增删元素。
注意LinkedList没有同步方法。如果多个线程同时访问一个List,则必须自己实现访问同步。一种解决方法是在创建List时构造一个同步的List:
List list = Collections.synchronizedList(new LinkedList(…));
ArrayList类
ArrayList实现了可变大小的数组,允许null元素,没有同步方法。擅长随机访问元素。与Array不同的是,Array可以容纳基本类型和对象,而ArrayList只能容纳对象。
每个ArrayList实例都有一个容量(Capacity),即用于存储元素的数组的大小。这个容量可随着不断添加新元素而自动增加,但是增长算法并没有定义。当需要插入大量元素时,在插入前可以调用ensureCapacity方法来增加ArrayList的容量以提高插入效率。
Vector类和Stack 类
作为遗留类不推荐使用。Vector非常类似ArrayList,两者都是基于索引的,内部由一个数组支持,都维护插入顺序。但是Vector是同步的。
Stack继承自Vector,提供5个额外的方法:基本的push和pop方法,还有peek方法得到栈顶的元素,empty方法测试堆栈是否为空,search方法检测一个元素在堆栈中的位置。
Set接口
Set是一种不包含重复的元素的Collection,即任意的两个元素e1和e2都有e1.equals(e2)=false,Set最多有一个null元素。
请注意:必须小心操作可变对象(Mutable Object)。如果一个Set中的可变元素改变了自身状态导致Object.equals(Object)=true将导致一些问题。
Set接口有以下几种实现:
(1)HashSet : 为快速查找设计的Set,主要的特点是:不能存放重复元素,而且采用散列的存储方法,所以没有顺序。这里所说的没有顺序是指元素插入的顺序与输出的顺序不一致。
(2)TreeSet : 保存次序的Set, 底层为树结构。使用它可以从Set中提取有序的序列。
(3)LinkedHashSet : 具有HashSet的查询速度,且内部使用链表维护元素的顺序(插入的次序)。于是在使用迭代器遍历Set时,结果会按元素插入的次序显示。
Map接口
常用有HashMap、LinkedHashMap、HashTable和TreeMap。详情请见:
Java8的HashMap详解(存储结构,功能实现,扩容优化,线程安全,遍历方法)。
这里主要讲一下,由于作为key的对象将通过计算其散列函数来确定与之对应的value的位置,因此任何作为key的对象都必须实现hashCode和equals方法。
hashCode和equals方法继承自根类Object,如果你用自定义的类当作key的话,要相当小心,按照散列函数的定义,如果两个对象相同,即obj1.equals(obj2)=true,则它们的hashCode必须相同,但如果两个对象不同,则它们的hashCode不一定不同,如果两个不同对象的hashCode相同,这种现象称为冲突,冲突会导致操作哈希表的时间开销增大,所以尽量定义好的hashCode()方法,能加快哈希表的操作。
如果相同的对象有不同的hashCode,对哈希表的操作会出现意想不到的结果(期待的get方法返回null),要避免这种问题,只需要牢记一条:要同时复写equals方法和hashCode方法,而不要只写其中一个。其实最好使用JDK提供的不可变类作为Map的key,可以避免自己实现hashCode()和equals()。
结语
以上就是集合框架相关的接口和类的介绍,最后要注意的是,尽量返回接口而非实际的类型,如返回List而非ArrayList,这样如果以后需要将ArrayList换成LinkedList时,客户端代码不用改变。这就是针对抽象编程。