EnumSet是一个专为枚举类设计的集合类。EnumSet中的所有值都必须是指定枚举类型的枚举值,该枚举值在创建EnumSet时显式或隐式的指定。EnumSet的集合元素也是有序的,它以枚举值在Enum类中定义顺序来决定集合元素的顺序。
EnumSet在内部以位向量的形式存储。不允许加入null元素,如果试图加入null元素,将抛出NullPointerException异常。EnumSet类没有暴露任何构造器来创建该类的实例,可以通过它提供的static方法来创建EnumSet对象。
public class TestEnumSet { @SuppressWarnings("unchecked") public static void main(String[] args) { //创建一个EnumSet集合,集合元素为Season枚举类的全部值 EnumSet enumset = EnumSet.allOf(Season.class); //[SPRING, SUMMER, FALL, WINTER] System.out.println(enumset); //创建一个具有指定元素类型的空枚举 set EnumSet enumset2 = EnumSet.noneOf(Season.class); System.out.println(enumset2); // [] enumset2.add(Season.SPRING); enumset2.add(Season.WINTER); System.out.println(enumset2); //[SPRING, WINTER] //以指定枚举值创建EnumSet集合 EnumSet enumset3 = EnumSet.of(Season.FALL, Season.SPRING); System.out.println(enumset3); // [SPRING, FALL] //创建一个最初包含由两个指定端点所定义范围内的所有元素的枚举 set EnumSet enumset4 = EnumSet.range(Season.SPRING, Season.WINTER); System.out.println(enumset4); //[SPRING, SUMMER, FALL, WINTER] //创建一个其元素类型与指定枚举 set相同的枚举 set,最初包含指定 set中所不包含的此类型的所有元素。 EnumSet enumset5 = EnumSet.complementOf(enumset4); System.out.println(enumset5); // [] } } enum Season { SPRING, SUMMER, FALL, WINTER }
当试图复制一个Collection集合里面的元素来创建EnumSet时,必须保证Collection集合里的所有元素都是同一个枚举类的枚举值。
@SuppressWarnings("unchecked") public static void testEnumSet() { Collection c = new HashSet(); c.add(Season.SPRING); c.add(Season.WINTER); //复制Collection集合中的所有元素创建EnumSet EnumSet set = EnumSet.copyOf(c); System.out.println(set); //[SPRING, WINTER] c.add("good"); c.add("nice"); //c集合里的元素不是全部都为枚举值,ClassCastException异常 set = EnumSet.copyOf(c); }
Set总结:
HashSet和TreeSet是Set的两个典型实现。HashSet的性能总是比TreeSet好(特别是常用的添加、查询等操作),因为TreeSet需要额外的红黑树算法来维护集合元素的次序。只有当需要一个保存排序的Set时,才应该使用TreeSet,否则都应该使用HashSet。
HashSet还有一个子类:LinkedHashSet,对于普通的插入、删除操作,LinkedHashSet要略微慢一点,这是由于维护链表所带来的额外开销造成的,又因为链表的存在,遍历LinkedHashSet会更快。
EnumSet是所有Set实现类中性能最好的,但它只能保存同一个枚举类的枚举值作为集合元素。
必须指出的是Set的三个实现类HashSet、TreeSet和EnumSet都是线程不安全的,如果有多条线程同时访问一个Set集合,并有超过一条线程修改了该Set集合,则必须手动保证该Set集合的同步性。通常可以通过Collections工具类的synchronizedSortedSet方法来包装该Set集合。此操作最好在创建时进行。
List接口代表一个有序集合,集合中每个元素都有其对应的顺序索引。允许使用重复元素,可以通过索引来访问指定位置的集合元素。
List接口作为Collection接口的子接口,可以使用Collection接口中的全部方法。相对于Set集合,List可以根据索引来插入、替换和删除集合元素。
List常规操作示例:
@SuppressWarnings("unchecked") public static void testList() { List list = new ArrayList(); list.add("广东"); list.add(new String("深圳")); System.out.println(list); // [广东, 深圳] list.add(1, "广东"); //广东 广东 深圳 for(int i = 0; i < list.size(); i ++) { System.out.println(list.get(i)); } //删除第二个元素 list.remove(1); //判断指定元素在list集合中的位置 //深圳这个字符串对象都是通过new String来添加的,不是同一个对象,依旧返回1, //List判断两个对象相等只要通过equals方法比较返回true即可 System.out.println(list.indexOf(new String("深圳"))); list.set(1, "佛山"); list.add("北京"); System.out.println(list); // [广东, 佛山, 北京] //将list集合中第一个元素(包括)到第三个元素(不包括)截取子串 System.out.println(list.subList(0, 2)); // [广东, 佛山] }
因为List集合可以根据位置索引来访问集合中的元素,因此集合List可以使用普通for循环来遍历集合元素。
List集合判断两个对象相等只要通过equals方法比较两个对象返回true即可。示例:
public class TestList { @SuppressWarnings("unchecked") public static void main(String[] args) { List list = new ArrayList(); list.add("tom"); list.add("lili"); list.add("john"); System.out.println(list); //[tom, lili, john] //两个对象相等便删除,对象相等的判断取决于equals返回true System.out.println(new D().equals(list.get(0))); //true //删除集合中D对象,将导致第一个元素被删除 list.remove(new D()); System.out.println(list); //[lili, john] list.remove(new D()); System.out.println(list); //[john] } } class D { @Override public boolean equals(Object obj) { return true; } }
从运行结果可以看出,试图删除一个D对象,List将会调用该D对象的equals方法依次与集合元素进行比较,如果返回true,List将会删除该元素。D类重写了equals方法,总是返回true,所以每次从集合中删除一个D对象,总是删除List集合中的第一个元素。
与Set只提供了一个iterator()方法不同,List还额外提供了一个listIterator()方法。该方法返回一个ListIterator对象。ListIterator接口继承了Iterator接口,提供了专门操作List的方法。
ListIterator接口定义如下:
package java.util; public interface ListIterator<E> extends Iterator<E> { boolean hasNext(); E next(); //返回该迭代器关联的集合是否还有上一元素 boolean hasPrevious(); //返回该迭代器的上一个元素 E previous(); int nextIndex(); int previousIndex(); void remove(); void set(E e); void add(E e); }
ListIterator与普通Iterator比较可以看到ListIterator增加了向前迭代的功能(Iterator只能向后迭代),而且ListIterator还可以通过add方法向List集合中添加元素(Iterator只能删除元素)。示例:
@SuppressWarnings("unchecked") public static void testList2() { String book[] = {"spring", "winner", "summer"}; List list = new ArrayList(); for(int i = 0; i < book.length; i ++) { list.add(book[i]); } System.out.println(list); ListIterator iter = list.listIterator(); while(iter.hasNext()) { System.out.println(iter.next()); //使用add方法向上一次迭代元素的后面添加一个新元素 iter.add("fall"); } //此时的list集合[spring, fall, winner, fall, summer, fall] System.out.println(list); //开始反向迭代,依次打印fall、summer、fall、winner、fall、spring //hasPrevious()如果以逆向遍历列表,列表迭代器有多个元素,则返回 true。 while(iter.hasPrevious()) { //返回列表中的前一个元素。 System.out.println(iter.previous()); } }
ArrayList和Vector作为List接口的两个典型实现,完全支持List接口的全部功能。
ArrayList和Vector类都是基于数组实现的List类,其内部封装了一个动态再分配的Object[]数组。每个ArrayList或Vector对象都有一个capacity属性,这个属性表示它们所封装的Object[]数组的长度,当添加元素超过长度时,capacity会自动增长。如果向ArrayList集合或Vector集合中添加大量元素时,可以使用ensureCapacity方法一次性的增加capacity。这可以减少增加重分配的次数,从而提高性能。如果创建ArrayList或Vector集合时没有指定capacity属性,其默认值为10。
ArrayList和Vector在用法上几乎完全相同。Vector是一个古老的集合(JDK 1.0就有了),那时候Java还没有提供系统的集合框架,所以Vector中有一些方法名很长的方法。JDK 1.2之后,java提供了系统的集合框架,将Vector改为List接口的实现,作为List实现之一,Vector中有一些重复的方法,方法名短的属于后面新增的。除此之外ArrayList和Vector的显著区别是:ArrayList是线程不安全的。Vector集合是线程安全的,无须程序保证该集合的同步性。因为Vector是线程安全的,所以比ArrayList性能低,我们也可以使用Collections工具类,将一个ArrayList变成线程安全的。
Vector还提供了一个Stack子类,用于模拟“栈”这种数据结构,后进先出,与java中其它集合一样,进栈出栈的都是Object。
@SuppressWarnings("unchecked") public static void teststack() { Stack s = new Stack(); s.push("1"); s.push("2"); s.push("3"); s.add("4"); s.add("5"); System.out.println(s); //[1, 2, 3, 4, 5] //访问第一个元素,但并不将其pop出栈 System.out.println(s.peek()); // 5 System.out.println(s); //[1, 2, 3, 4, 5] //访问第一个元素,将其pop出栈 System.out.println(s.pop()); // 5 System.out.println(s); // [1, 2, 3, 4] }
除此之外,List还有一个LinkedList的实现,它是一个基于链表实现的List类,对于顺序访问集合中的元素进行了优化,特别是插入、删除元素时速度非常快。因为LinkedList即实现了List接口,又实现了Deque接口(双向队列) 。
有一个操作数组的工具类Arrays。它有一个asList方法,public static <T> List<T> asList(T... a),返回一个受指定数组支持的固定大小的列表,此方法还提供了一个创建固定长度的列表的便捷方法,该列表被初始化为包含多个元素: 如 List<String> stooges = Arrays.asList("Larry", "Moe", "Curly");
@SuppressWarnings("unchecked") public static void testArrays() { List fiexdList = Arrays.asList("spring", "winner"); System.out.println(fiexdList); //[spring, winner] System.out.println(fiexdList.getClass()); //Arrays$ArrayList //试图增加删除元素都将引发UnsupportedOperationException异常 fiexdList.add("fall"); fiexdList.remove("spring"); }
通过asList转换为的这个集合既不是ArrayList实现类的实例,也不是Vector实现类的实例,而是Arrays的内部类ArrayList的实例。Arrays.ArrayList是一个固定长度的List集合,程序只能遍历访问该集合里的元素,不可增加、删除该集合里的元素。
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, Serializable
LinkedList类是一个比较特殊的类,它即实现了List接口,可以根据索引来随机访问集合中的元素。除此之外,它又实现了Deque接口,Deque接口是Queue接口的子接口,它代表一个双向队列,该接口中定义了一些可以双向操作队列的方法,所以,LinkedList不仅可以当成双向队列使用,也可以当成栈使用,因为该类里还包含了pop(出栈)和push(入栈)两个方法。 LinkedList通常用法示例:
@SuppressWarnings("unchecked") public static void testLinkedList() { LinkedList books = new LinkedList(); //将字符串元素加入到队列的尾部 books.offer("深圳"); //将一个字符串元素入栈 books.push("广东"); //将字符串元素加入到队列的头部 books.offerFirst("佛山"); System.out.println(books); //[佛山, 广东, 深圳] for(int i = 0; i < books.size(); i ++) { System.out.println(books.get(i)); } //访问并不删除队列的第一个元素 System.out.println(books.peekFirst()); //佛山 //访问并不删除队列的最后一个元素 System.out.println(books.peekLast()); //深圳 //采用出栈的方式将第一个元素出队列 System.out.println(books.pop()); //佛山 //队列中第一个元素被删除 System.out.println(books); //[广东, 深圳] //访问并删除队列的最后一个元素 System.out.println(books.pollLast()); //深圳 System.out.println(books); //[广东] }
LinkedList与ArrayList、Vector的实现机制完全不同,ArrayList与Vector内部以数组的形式来保存元素,因此随机访问集合元素上有较好的性能。而LinkedList内部以链表的形式来保存集合中的元素,因此随机访问集合时性能较差。但在插入、删除元素时性能非常出色(只需改变指针所指地址即可)。实际上,Vector因为实现了线程同步,所以各方面性能都有所下降。
因为数组以一块连续内存区来保存所有数组元素,所以数组在随机访问时性能最好。所有内部以数组为底层实现的集合在随机访问时也有较好性能;而内部以链表作为底层实现的集合在插入、删除操作时有较好的性能。进行迭代操作时,以链表作为底层实现的集合也比以数组作为底层实现的集合性能要好。
总结:
使用List集合有以下建议:
● 如果需要遍历List集合元素,对于ArrayList、Vector集合,应该使用随机访问方法(get)来遍历集合元素,这样性能更好。对于LinkedList集合,使用迭代器(Iterator)来遍历集合元素。
● 如果需要经常执行插入、删除操作来改变List集合的大小,则应该使用LinkedList集合,而不是ArrayList。使用ArrayList、Vector集合将需要经常重新分配内部数组的大小,其时间开销大。
● 如果有多条线程要同时访问List集合中的元素,可以考虑使用Vector这个同步实现。
Queue用于模拟队列这种数据结构,队列通常是“先进先出”的容器。通常,队列不允许随机访问对列中的元素。
Queue接口有两个常用的实现类:LinkedList和PriorityQueue。
PriorityQueue实现类
PriorityQueue是一个比较标准的队列实现类,PriorityQueue保存队列元素的顺序并不是按加入队列的顺序,而是按队列元素的大小进行重新排列。因此当调用peek方法或者poll方法来取出队列中的元素时,并不是取出最先进入队列的元素,而是取出队列中最小的元素。从这个意义上来看,PriorityQueue已经违反了队列的基本原则:先进先出(FIFO)。
@SuppressWarnings("unchecked") public static void testPriorityQueue() { Queue queue = new PriorityQueue(); queue.offer(6); queue.offer(-5); queue.offer(9); queue.offer(0); //输出队列,并不是按照元素的加入顺序排列,而是按照元素的大小顺序排列 System.out.println(queue); //[-5, 0, 9, 6] //访问队列中的第一个元素,其实就是队列中最小元素 System.out.println(queue.peek()); // -5 }
PriorityQueue不允许插入null元素,它还需要对队列元素进行排序,队列元素排序有两种方式:
● 自然排序:采用自然排序的PriorityQueue集合中的元素必须实现了Comparable接口,而且应该是同一个类的多个实例,否则可能导致ClassCastException异常。
● 定制排序:采用定制排序时不需要队列元素实现Comparable接口。