这篇博文主要介绍Java中集合是如何组织的(也就是Java集合在类库中的层次结构)
Java集合框架概述:
Java最初的版本只为最常用的数据结构提供了很少一组类:Vector,Stack,Hashtable,BitSet与Enumeration接口,其中Enumeration接口提供了一种用于访问任意容器中各个元素的抽象机制。后来Java1.2推出,类库设计人员推出了一组功能完善的数据结构,他们希望自己的类库可以像C++的STL那样功能强大,并且比STL更加简单和容易学习,于是他们做出了很大的努力。下面就正式介绍这些类库以及特性
1:将集合的接口与实现分离:
Java集合类库将接口与实现分离。
就队列的实现而言:
当在程序中使用队列时,一旦构建了集合就不需要知道究竟使用了哪种实现,因此,只有在构建集合对象时,使用具体的类才有意义,可以使用接口类型存放集合的引用:
Queue<Customer> expressLane=new CircularArrayQueue<>(100);
expressLane.add(new Customer("Harry"));
利用这种方式,一旦改变了想法,可以轻松地使用另外一种不同的实现。只需要对程序的一个地方做出修改,即调用构造器的方法。如果觉得LinkedListQueue是个更好的选择,就可以将代码修改为:
Queue<Customer> expressLane=new LinkedArrayQueue<>(100);
expressLane.add(new Customer("Harry"));
循环数组的效率较高,但是由于数组是有限的,因此容量有限,如果程序中要收集的对象数量没有上限,就最好使用链表来实现
另外还有一组名字以Abstract开头的类,这些类是为类库设计者而设计的。如果想要实现自己的集合类,会发现扩展Abstract类要比其他类简单。
2:Collection接口:
在Java类库中,集合类的基本接口是Collection接口。这个接口有两个基本方法
public interface Collenction<E>{
boolean add(E element);
Iterator<E> iterator();
...
}
add用于向集合中添加元素,如果添加成功则返回true,否则返回false
iterator方法返回了一个实现了Iterator接口的对象。可以使用这个迭代器对象依次访问集合中的元素。
3:迭代器:
Iterator接口包含4个方法:
public interface Iterator<E>{
E next();
boolean hasNext();
void remove();
default void forEachRemaining(Consumer<? super E> action);
...
}
通过反复调用next方法,可以逐个访问集合中的每个元素。但是如果到达了集合的末尾,next方法将会抛出一个NoSuchElementException。因此,在调用next之前调用hasNext方法。如果迭代器对象还有多个供访问的元素,这个方法就返回true。如果想要查看集合中的所有元素,就请求一个迭代器,并在hasNext返回true时反复调用next方法。
当然也可以使用for each循环完成同样的操作,对于这种循环,编译器会简单的翻译为带有迭代器的循环,“for each”循环可以与任何实现了Iterable接口的对象一起工作,这个接口只包含一个方法:Iterator<E> iterator();因为Collection接口扩展了Iterable接口,因此,对于标准类库中的任何集合都可以使用“for each”循环
在Java SE 8中,可以不用使用循环。因为可以调用forEachRemaining方法并提供一个lambda表达式。将对迭代器的每一个元素调用这个lambda表达式,直到没有元素为止。像这样:iterator.forEachRemaining(element->对元素进行的操作)。
元素被访问的顺序取决于集合的类型。如果对ArrayList进行迭代,迭代器将从索引0开始顺序迭代到列表尾,如果对HashSet进行迭代,则每个元素将按照某种随机次序出现。
另外要注意一点,在以前版本的Enumeration接口中有两个方法nextElement和hasMoreElement的作用与next和hasNext完全一样,唯一的不同就是名字不同,可以酌情使用。
下面是最后一点需要注意的:应该认为Java的迭代器是位于两个元素之间。当调用next时,迭代器就越过下一个元素,并返回刚刚越过的那个元素的引用。Java集合的迭代器不像C++那样,可以执行++和--甚至与数字进行运算的操作,只可以使用next方法从前向后移动。如果要对集合中的元素进行删除操作,可以使用remove方法,但是要注意,每个remove方法的前面至少要保证执行过一次next方法,否则,remove方法将会抛出一个IllegalStateException异常。
下面是一个示例:
代码:
package Collection;
import java.util.*;
public class Test1 {
public static void main(String[] args) {
Collection<String> c=new ArrayList<String>();
c.add("zhang");
c.add("yan");
c.add("jie");
c.add("yan");
c.add("zhi");
System.out.println("原字符串");
for(String e:c) {
System.out.println(e);
}
Iterator<String> iter=c.iterator();
while(iter.hasNext()) {
String e=iter.next();
if(e.contains("a")) {//剔除字符串中存在a字母的串
iter.remove();
}
}
System.out.println("剔除字符串中存在a字母的串后:");
iter=c.iterator();
iter.forEachRemaining(e->System.out.println(e));
}
}
运行结果:
4.泛型实用方法:
由于Collection和Iterator都是泛型接口,可以编写操作任何集合类型的实用方法。其中在这些方法中有一些是十分有用的,于是Java类库的开发者就将这些实用方法提供给用户使用,而不用用户自己费脑筋去写了。事实上,Java的Collection接口中声明了许多这样的方法,每一个继承Collection的类都必须实现这些方法:
下面是这些方法中的一部分:
int size();
boolean isEmpty();
boolean contains(Object obj);
boolean containsAll(Collection<?> c);
boolean equals(Object other);
boolean addAll(Collection<? extends E> from);
boolean remove(Object obj);
boolean removeAll(Collection<?> c);
void clear();
boolean retainAll(Collection<?> c);
Object[] toArray();
<T> T[] toArray(T[] arrayToFill);
这些方法中功能非常明确,一眼就能看出有什么功能,如果看不懂也可以查文档。
但是,Collection中的方法确实太多了,如果实现Collection接口的每一个类都要提供如此多的例行方法将会是一件很令人抓狂的事情,于是,为了方便实现者,Java类库提供了另外一个类:AbstractCollection,它将基础方法size和iterator抽象化了,但是在此提供了例行方法。比如说:
public abstract class AbstractCollection<E> implements Collection<E>{
//...
public abstract Iterator<E> iterator();
public boolean contains(Object obj) {
for(E element:this) {
if(element.equals(obj)) {
return true;
}
}
return false;
}
//...
}
5.集合框架中的接口:
额,说了这么多,好像没提到类库中的这些接口之间的层次,于是,下面就给出这些层次
集合有两个基本接口:Collection和Map
使用此方法:boolean add(E element)在集合中插入元素,不过,映射集合要使用V put(K key,V value)来插入。
使用迭代器访问集合元素,或者有些集合支持随机访问,比如List,可以使用 E get(int index)来进行访问,不过,从映射中读取值则要使用另一种get方法:V get(K key)。
以List为例,它提供了许多支持随机访问的方法:例如:
void add(int index,E element);
void remove(int index);
E get(int index);
E set(int index,E element);
ListIterator是Iterator的一个子接口。它定义了一个方法用于在迭代器位置前面增加一个元素。void add(E elemrnt);
为了避免对链表进行随机访问,JavaSE1.4引入了一个标记接口RandomAccess,这个接口不包含任何方法,不过可以使用它来测试一个特定的集合是否支持高效的随机访问:
if(c instanceof RandomAccess){
//使用随机访问
}else{
//不能使用随机访问
}
Set接口等同于Collection接口,但是其方法的行为具有更加严谨的定义,例如:add方法不能添加同样的元素,equals方法忽略元素顺序,hashCode方法只要保证两个集合元素相同就会得到相同的哈希码等等。。。
SortedSet和SortedMap接口会提供用于排序的比较器对象,这两个接口定义可以得到集合子集视图的方法。
JavaSE 6引入了接口Navigable和NavigableMap,其中包含一些用于搜索和遍历有序集和映射的方法。TreeSet和TreeMap则实现了这些方法。