Set 具有与 Collection 完全一样的接口,因此没有任何额外的功能,不像前面有两个不同
的 List。实际上 Set 就是 Collection,只是行为不同。(这是继承与多态思想的典型应用:
表现不同的行为。)Set 不保存重复的元素(至于如何判断元素相同则较为复杂,稍后便会
看到)。
Set (interface)
存入 Set 的每个元素都必须是唯一的,因为 Set 不保
存重复元素。加入 Set 的元素必须定义 equals()方法
以确保对象的唯一性。Set 与 Collection 有完全一样
的接口。Set 接口不保证维护元素的次序。
HashSet*
为快速查找设计的 Set。存入 HashSet 的对象必须定
义 hashCode()。
TreeSet
保持次序的 Set,底层为树结构。使用它可以从 Set
中提取有序的序列。
LinkedHashSet
(JDK 1.4)
具有 HashSet 的查询速度,且内部使用链表维护元素
的顺序(插入的次序)。于是在使用迭代器遍历 Set
时,结果会按元素插入的次序显示。
下例并没有演示Set 能够做的所有事情,因为它的接口与 Collection 相同,所以有些在前
例中练习过了。这里演示的只是 Set 独有的行为:
//:c11:Set1.java
// Things youcan do with Sets.
import com.bruceeckel.simpletest.*;
import java.util.*;
public class Set1 {
private static Test monitor = new Test();
static void fill(Set s) {
s.addAll(Arrays.asList(
"one twothree four five six seven".split(" ")));
}
public static void test(Set s) {
// Stripqualifiers from class name:
System.out.println(
s.getClass().getName().replaceAll("\\w+\\.", ""));
fill(s); fill(s); fill(s);
System.out.println(s); // No duplicates!
// Add anotherset to this one:
s.addAll(s);
s.add("one");
s.add("one");
s.add("one");
System.out.println(s);
// Looksomething up:
System.out.println("s.contains(\"one\"): " +
s.contains("one"));
}
public static void main(String[] args) {
test(new HashSet());
test(new TreeSet());
test(new LinkedHashSet());
monitor.expect(new String[] {
"HashSet",
"[one,two, five, four, three, seven, six]",
"[one,two, five, four, three, seven, six]",
"s.contains(\"one\"): true",
"TreeSet",
"[five,four, one, seven, six, three, two]",
"[five,four, one, seven, six, three, two]",
"s.contains(\"one\"): true",
"LinkedHashSet",
"[one,two, three, four, five, six, seven]",
"[one,two, three, four, five, six, seven]",
"s.contains(\"one\"): true"
});
}
} ///:~
例子中向Set 添加了重复的值,但是打印的结果说明,对每一个值 Set 只接受一份实例。
运行此程序,你会注意到,HashSet 维护的元素次序不同于 TreeSet 和 LinkedHashSet,
因为它们保存元素的方式各有不同,使得以后还能找到元素。(TreeSet 采用红黑树的数
据结构排序元素,HashSet 则采用散列函数,这是专门为快速查询设计的。LinkedHashSet
内部使用散列以加快查询速度,同时使用链表维护元素的次序,使得看起来元素是以插入
的顺序保存的。)生成自己的类时,注意 Set 需要维护元素的存储顺序,这意味着你必须
实现 Comparable 接口,并且定义 compareTo()方法。参见下例:
//:c11:Set2.java
// Putting yourown type in a Set.
import com.bruceeckel.simpletest.*;
import java.util.*;
public class Set2 {
private static Test monitor = new Test();
public static Set fill(Set a, int size) {
for(int i = 0; i < size; i++)
a.add(new MyType(i));
return a;
}
public static void test(Set a) {
fill(a, 10);
fill(a, 10); // Try to add duplicates
fill(a, 10);
a.addAll(fill(new TreeSet(), 10));
System.out.println(a);
}
public static void main(String[] args) {
test(new HashSet());
test(new TreeSet());
test(new LinkedHashSet());
monitor.expect(new String[] {
"[2 , 4 ,9 , 8 , 6 , 1 , 3 , 7 , 5 , 0 ]",
"[9 , 8 ,7 , 6 , 5 , 4 , 3 , 2 , 1 , 0 ]",
"[0 , 1 ,2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ]"
});
}
} ///:~
本章稍后会介绍如何定义 equals()和 hashCode()。使用以上两种 Set 都必须为你的类定
义 equals(),而 hashCode(),只在你的类会被 HashSet 用到的情况下才是必要的(这
种可能性很大,因为HashSet 通常是使用 Set 的第一选择)。无论如何,作为一种编程风
格,当你重载equals()的时候,就应该同时重载 hashCode()。本章稍后会详细介绍这个
过程。
注意在 compareTo()中,我并没有使用“简单而明显”的 return i-i2。虽然这是一个常
犯的编程错误,但是如果 i 与 i2 正巧是“无符号”的 int时(如果 Java 有“无符号”unsigned
这个关键字的话,其实没有),此语句也能正常工作。如果是 Java 的有符号整数 int 则无
法确保正确,因为int 不够大,不足已表现两个有符号整数 int 的差。例如 i 是很大的正整
数,而 j 是很大的负整数,i-j就会溢出并且返回负值,这就不正确了。