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() 的方式:
- 对象内每个有意义的属性 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步计算出来多个 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 基本相同。