7_0_1

7.1 Collection

Cllection接口里常用的方法:

  • boolean add(Object o)
  • boolean addAll(Collection c)
  • void clear():清除集合里的所有元素,将集合长度变为0
  • boolean contains(Object o)
  • boolean containsAll(Collection c)
  • boolean isEmpty()
  • Iterator iterator()
  • boolean remove(Object o):删除集合中指定元素o,若有元素被删除,则返回true
  • boolean removeAll(Collection c):从集合中删除集合c里包含的所有元素,相当于该集合减去集合c。若有元素被删除,则返回true
  • boolean retainAll(Collection c):从集合删除集合c里不包含的所有元素,相当于取两个集合的交集。如果该操作改变了调用该方法的集合,则返回true
  • int size():返回该集合里元素的个数
  • Object[] toArray():把集合转换成一个数组,所有集合元素变成对应的数组元素

7.1.1 使用Iterator接口遍历集合元素

Iterator接口里的三个方法:

  • boolean hasNext():如果被迭代的集合元素还没有被遍历,则返回true。
  • Object next():返回集合里下一个元素
  • void remove():删除集合里上一次next方法返回的元素

例子:

Collection books = new HashSet();
books.add("第一本");
books.add("第二本");
Iterator it = books.iterator();
while(it.hasNext()){
    Stirng book = (Stirng) it.next();
    it.remove();
}

当使用Iterator进行迭代时,Iterator并不是把集合元素本身传给了迭代变量,而是只传递了元素的值,所以修改迭代变量不会影响集合里的元素。

在迭代过程中不能修改Collection集合,只能使用remove()方法,否则会报错。

使用 foreach 遍历集合的过程中,也不能修改集合,否则会引发 ConcurrentModificationException 异常。

Iterator 迭代器采用的是快速失败(fail-fast)机制,一旦在迭代过程中检测到该集合已经被修改(通常是程序中其他线程修改的),程序立即引发 ConcurrentModificationException 异常。

7.2 Set 接口

Set 集合特点:不允许包含重复的元素;根据 equals 方法判断两个对象是否相等。

7.2.1 HashSet 类

HashSet 的特点: 元素无须、不能重复、不是同步的、值可以是null。

HashSet 集合判断两个元素相等的标准是两个对象通过equals方法比较相等,并且 hashCode() 的返回值也相等。

如果一个类的 equals() 方法,也应该重写其 hashCode() 方法,使其两个对象的通过该 equals() 和 hashCode() 比较的值相等。

重写 hashCode() 的方式:

  1. 对象内每个有意义的属性 f (即每个用作 equals() 比较标准的属性)计算出一个int类型的 hashCode 值。计算方法如下表:
属性类型 计算方式
boolean hashCode = (f ? 0 : 1)
整型(byte、short、char、int) hashCode = (int)f
long hashCode = (int)(f ^ (f >>> 32))
float hashCode = Float.floatToIntBits(f)
double long l = Double.doubleToLongBits(f); hashCode = (int)(l ^ (l >>> 32))
普通引用类型 hashCode = f.hashCode()
  1. 用第1步计算出来多个 hashCode 组合计算出一个 hashCode 值返回。为了避免直接相加产生偶然相等,可以通过为各属性乘以任意一个质数后再相加。
return f1.hashCode() * 17 + (int)f2 * 13;

如果修改 HetSet 集合中的对象,有可能导致该对象与集合中其他对象相等,从而导致 HashSet 无法准确访问该对象。例如:

class R {
    int count;
    public R(int count) {
        this.count = count;
    }
    public boolean equals(Object) {
        if (obj instanceof R) {
            R r = (R)obj;
            if (r.count == this.count) {
                return true;
            }
        }
        return false;
    }
    public int hashCode() {
        return this.count;
    }
}

public class HashSetTest {
    public static void main(String[] args) {
        R r1 = new R(1);
        R r2 = new R(2);
        R r3 = new R(3);
        HashSet hs = new HashSet();
        hs.add(r1);
        hs.add(r2);
        hs.add(r3);
        System.out.println(hs);
        r1.count = 3;
        hs.remove(r1);
        System.out.println(hs);
    }
}

运行上面的程序可以看到,HashSet 集合中r3被删除,而不是r1。

7.2.2 LinkedHashSet

LinkedHashSet 是 HashSet 的子类,也是根据元素 hashCode 值来决定元素的存储位置,但它同时使用链表维护集合里元素插入的次序。

对于普通插入、删除等操作,LinkedHashSet 比 HashSet 要略微慢一些;但遍历的话 LinkedHashSet 会更快。

7.2.3 TreeSet 类

TreeSet 是 SortedSet 接口的唯一实现。

TreeSet 几个额外的方法:

  • Comparator comparator():返回当前 Set 使用的 Comparator;或者返回 null,表示以自然方式排序。
  • Object first():返回集合中的第一个元素。
  • Object last()
  • Object lower(Object e):返回集合中位于指定元素之前的元素(e不需要是 TreeSet 的元素)。
  • Object higher(Object e)
  • SortedSet subSet(fromElement, toElement):返回此 Set 的子集合,范围从 fromElement(包含)到 toElement (不包含)。
  • SortedSet headSet(toElement):返回此 Set 的子集,由小于 toElement 的元素组成。
  • SortedSet tailSet(fromElement):返回此Set的子集,由大于或等于 fromElement 的元素组成。

TreeSet 采用红黑树的数据结构对元素进行排序。

TreeSet 支持自然排序和定制排序,默认情况下采用自然排序

7.2.3.1 自然排序

TreeSet 通过元素的 compareTo(Object obj) 方法的比较结果,将元素按升序排序。

Comparable 接口里定义了一个 compareTo(Object obj) 方法。例如 obj1.compareTo(obj2),若该方法返回0,则表明两个对象相等;若返回一个正数,则表明 obj1 大于 obj2;若返回负数,则表明 obj1 小于 obj2。

实现了 Comparable 接口的常用类:

  • BigDecimal、BigInteger 以及所有数值型对应的包装类:按它们对应的数值大小进行比较。
  • Character:按字符的 UNICODE 值进行比较。
  • Boolean:true 对应的包装类实例大于 false 的包装类实例。
  • String:按字符串中字符的 UNICODE 值进行比较。
  • Date、Time:后面的时间、日期比前面的大。

如果试图把一个对象添加进 TreeSet 时,则该对象的类必须实现 Comparable 接口。

注意:
向 TreeSet 集合中添加第一个对象时,由于 TreeSet 里没有任何元素,所以该对象不实现 Comparable 也不会出现任何问题(但此时从 TreeSet 中取出元素时,依然会引发 ClassCastException );当添加第二个对象时,TreeSet 会调用该对象的 compareTo(Object obj) 方法与集合中的其他元素进行比较,若该对象的类没有实现 Comparable 接口,就会引发 ClassCastException 异常。

​ 大部分类(如 String、Date 等类)在实现 compareTo 方法时,都需要将被比较的对象强制类型转换成相同类型,因为只有相同类的两个实例才会比较大小。

​ 如果自定义类实现了 Comparable 接口,且实现 compareTo(Object obj) 方法时没有进行强制类型转换,则可以向 TreeSet 中添加多种类型的对象。但当试图操作 TreeSet 里的集合数据时,不同类型的元素依然会发生 ClassCastException 异常。

TreeSet 集合判断两个对象相等的标准是:两个对象通过equals方法比较返回true,且通过 compareTo(Object obj) 比较返回0

如果向 TreeSet 中添加可变对象,并且改变了集合中可变对象的属性,导致该它与其它对象的大小顺序发生了改变,TreeSet 不会再次调整它们的顺序,并且删除被改变了的对象时会失败。

7.2.3.2 定制排序

Comparator 接口里包含一个 int compare(T o1, T o2) 方法,用于比较 o1 和 o2 的大小:如果返回正整数,则表明 o1 大于 o2;如果返回0,则表明 o1 等于 o2 ;如果返回负整数,则表明 o1 小于 o2。

降序排序用法如下:

class M {
    int age;
    public M(int age) {
        this.age = age;
    }
    public String toString(){
        return "M对象(age:" + age + “)”;
    }
}

public class TreeSetTest {
    public static void main(String[] agrs) {
        TreeSet ts = new TreeSet(new Comparator(){
           public int compare(Object o1, Object o2) {
               M m1 = (M)o1;
               M m2 = (M)o2;
               if (m1.age > m2.age) {
                   return -1;
               } else if (m1.age == m2.age) {
                   return 0;
               } else {
                   return 1;
               }
           } 
        });
        ts.add(new M(5));
        ts.add(new M(-3));
        ts.add(new M(9));
        System.out.println(ts);
    }
}

当通过 Comparator 对象实现 TreeSet 的定制排序时,依然不可以添加不同类型的对象,否则会引发 ClassCastException 异常。

7.2.4 EnumSet 类

特点:

  • EnumSet 中所有的值都必须是指定枚举类型的枚举值。
  • 元素有序,以枚举值在枚举类内被定义的顺序来决定。
  • 在内部以位向量的形式存储,占用内存小,运行效率好。
  • 不允许加入 null 元素,否则会抛出 NullPointerException 异常。但测试集合是否包含 null 或删除 null 都不会报错,只是删除操作将返回 false。
  • 只能用 EnumSet 提供的 static 方法来创建 EnumSet 对象。

如下是几个常用的 static 方法用于创建 EnumSet 对象:

  • static EnumSet allOf(Class elementType):创建一个包含指定枚举类里所有枚举值的 EnumSet 集合。
  • static EnumSet complementOf(EnumSet s):根据 s 集合中元素的枚举类型,创建一个包含该枚举类里所有枚举值的 EnumSet 集合。
  • static EnumSet copyOf(Collection c):复制集合 c 中所有元素来创建新的 EnumSet 对象,集合 c 中所有元素必须是同一个枚举类的枚举值。
  • static EnumSet copyOf(EnumSet s):创建一个与指定 EnumSet 具有相同元素类型、相同集合元素的 EnumSet 集合。
  • static EnumSet noneOf(Class elementType):创建一个元素类型为指定枚举类型的空 EnumSet 集合。
  • static EnumSet of(E first, E... rest):创建一个包含传入的枚举值的 EnumSet 集合,传入的枚举值必须属于同一个枚举类。
  • static EnumSet range(E from, E to):创建包含从 from 枚举值,到 to 枚举值范围内所有枚举值的 EnumSet 集合。

7.3 List 接口

List 特点:

  • List 集合允许元素重复
  • 可以通过索引来访问指定位置的集合元素,第一次添加的元素索引为0,第二次添加的索引为1,依此类推。
  • 通过 equals 方法判断两个元素是否相同。

List 集合里一些根据索引来操作集合元素的方法:

  • void add(int index, Object element):将元素 element 插入到 List 集合的 index 处。
  • boolean addAll(int index, Collection c):将集合 c 所包含的所有元素都插入在 List 集合的 index 处。
  • Object get(int index):返回集合 index 索引处的元素。
  • int indexOf(Object o):返回对象 o 在 List 集合中第一次出现的位置索引,若无此对象,则返回-1。
  • int lastIndexOf(Object o):返回对象 o 在 List 集合中最后一次出现的位置索引。
  • Object remove(int index):删除并返回 index 索引处的元素。
  • Object set(int index, Object element):将 index 索引处的元素替换成 element 对象,返回被替换的元素。
  • List subList(int fromIndex, int toIndex):返回从索引 fromIndex (包含)到索引 toIndex (不包含)处所有集合元素组成的子集合。

7.3.1 ListIterator 接口

List 额外提供了一个 listIterator() 方法,该方法返回一个 ListIterator 对象。

ListIterator 接口继承了 Iterator 接口,并提供了专门操作 List 的方法:

  • boolean hasPrevious():返回该迭代器关联的集合是否还有上一个元素。
  • Object previous():返回该迭代器的上一个元素。
  • void add(E e):将指定元素 e 插入到 next() 返回的元素之前,previous() 返回的元素之后。完成插入后,若调用 previous() 方法,则会返回新插入的元素 e。

7.3.2 ArrayList 和 Vector 实现类

由于 Vector 是一个古老的集合(从 JDK1.0 就有了),所以有很多缺点,通常应当少用 Vector。

ArrayList 是线程不安全的,Vector 是线程安全的,但 Vector 性能比 ArrayList 低。

7.3.2.1 capacity属性

ArrayList 和 Vector 类都是基于数组实现的 List 类,所以两个类都封装一个动态再分配的 Object[] 数组。每个 ArrayList 和 Verctor 对象有一个 capacity 属性,表示 Object[] 的长度。当向 ArrayList 或 Vector 中添加元素时,capacity 会自动增加。

如果向 ArrayList 或 Vector 集合中添加大量元素时,可使用 ensureCapacity 方法一次性增加 capacity,这样可以减少增加重分配的次数,以提高性能。

如果开始就知道 ArrayList 集合或 Vector 集合需要保存多少个元素,则可以在创建时就指定 capacity 的大小。如果创建空 ArrayList 或 Vector 集合时不指定 capacity 属性,则该属性默认为10

ArrayList 和 Vector 提供的两个方法来操作 capacity 属性:

  • void ensureCapacity(int minCapacity):将 capacity 增加 minCapacity。
  • void trimToSize():将 capacity 调整为列表的当前大小。程序可调用该方法来减少 ArrayList 或 Vector 集合对象存储空间。

7.3.2.2 Vector 的 Stack 子类

Vector 提供了一个 Stack 子类,模拟了“栈”数据结构。

Stack 类里的几个方法:

  • Object peek():返回“栈”的第一个元素,但并不将该元素”出栈“。
  • Object pop():返回”栈“的第一个元素,并将该元素”出栈“。
  • void push(Object item):将元素 item ”入栈“。

例子:

Stack v = new Stack();
v.push("test1");
v.push("test2");
System.out.println(v.peek());
System.out.println(v.pop());

7.3.3 固定长度的 List:Arrays.ArrayList

Arrays 工具类提供了 asList(Object... a) 方法,该方法可以把一个数组或指定个数的对象转换成一个 List 集合,这个 List 集合既不是 ArrayList 的实例,也不是 Vector 的实例,而是 Arrays 的内部类 ArrayList 的实例

Arrays.ArrayList 长度固定,只能遍历访问该集合里的元素,不可增加、删除该集合里的元素,否则会引发 UnsupportedOperationException 异常。

7.4 Queue 接口

Queue 用于模拟队列这种数据结构。通常,队列不允许随机访问队列中的元素。

Queue 接口里定义的方法:

  • boolean add(Object e):将指定元素加入此队列的尾部,加入成功,返回true;若因空间不足加入失败,则会抛出 IllegalStateException 异常。
  • Object element():获取队列头部的元素,但不会删除该元素。若队列为空,则抛出 NoSuchElementException 异常。
  • boolean offer(Object e):将指定元素加入到队列的尾部。当使用有容量限制的队列时,此方法通常比 add(Object e) 方法更好,因为此方法加入元素成功,则返回 true,若因容量不足加入失败,则返回false。
  • Object peek():获取队列头部的元素,但不删除该元素。若队列为空,则返回 null。
  • Object poll():获取队列头部的元素,并删除该元素。若队列为空,则返回 null。
  • Object remove():删除队列头部的元素,并返回该元素。

7.4.1 Deque 接口

Deque 接口是 Queue 接口的子接口,它代表一个双向队列,Deque 接口里定义了一些可以操作队列的方法:

  • boolean addFirst(Object e):将元素 e 插入该双向队列的开头。
  • boolean addLast(Object e):将元素 e 插入该双向队列的末尾。
  • Iterator descendingIterator():返回以该双向队列对应的迭代器,该迭代器以逆序来迭代队列中的元素。
  • Object getFirst():获取、但不删除双向队列的第一个元素。
  • Object getLast():获取、但不删除双向队列的最后一个元素。
  • boolean offerFirst(Object e):将元素 e 插入该双向队列的开头。
  • boolena offerLast(Object e):将元素 e 插入该双向队列的末尾。
  • Object peekFirst():获取、但不删除该双向队列的第一个元素;若此队列为空,则返回 null。
  • Object peekLast():获取、但不删除该双向队列的最后一个元素;若此队列为空,则返回 null。
  • Object pollFirst():获取并删除该双向队列的第一个元素;若此队列为空,则返回 null/
  • Object pollLast()
  • Object pop():pop 出该双向队列所表示的栈中第一个元素。
  • void push(Object e):将一个元素压入该双向队列所表示的栈中(即该双向队列的头部)。
  • Object removeFirst():获取、并删除该双向队列的第一个元素。
  • Object removeLast()
  • Object removeFirstOccurrence(Object o):删除该双向队列的第一次出现的元素 o。
  • Object removeLastOccurrence(Object o)

7.4.2 LinkedList 实现类

LinkedList 实现了 List 接口和 Deque 接口。

LinkedList 内部以链表的形式来保存集合中的元素,因此随机访问元素时性能较差,插入、删除元素时较好。

对于所有内部基于数组的集合实现,例如 ArrayList、Vector 等,使用随机访问的速度比使用 Iterator 迭代访问的性能要好,因为随机访问会被映射成对数组元素的访问。

数组、ArrayList、Vector、LinkedList 的性能差异:

实现机制 随机访问排名 迭代操作排名 插入操作排名 删除操作排名
数组 连续内存区保存元素 1 不支持 不支持 不支持
ArrayList 内部以数组保存元素 2 2 2 2
Vector 内部以数组保存元素 3 3 3 3
LinkedList 内部以链表保存元素 4 1 1 1

7.4.3 PriorityQueue 实现类

PriorityQueue 保存队列元素的顺序不是按加入队列的顺序,而是按队列元素的大小进行重新排序。

PriorityQueue 不允许插入 null 元素。

排序方式:

  • 自然排序:PriorityQueue 集合中的元素必须实现 Comparable 接口,而且应该都属于同一个类,否则可能导致 ClassCastException 异常。
  • 定义排序:创建 PriorityQueue 时传入一个 Comparator 对象,该对象负责对队列中所有元素进行排序。此时,队列中的元素不要求实现 Comparable 接口。

PriorityQueue 的排序方 式与 TreeSet 基本相同。