一、树形结构:
public interface Set<E> extends Collection<E>{}
public abstract class AbstractSet<E> extends AbstractCollection<E> implements Set<E>{}
public class CopyOnWriteArraySet<E>extends AbstractSet<E>implements Serializable{}
public abstract class EnumSet<E extends Enum<E>>extends AbstractSet<E>implements Cloneable, Serializable{}
public class HashSet<E>extends AbstractSet<E>implements Set<E>, Cloneable, Serializable{}
public final class JobStateReasonsextends HashSet<JobStateReason>implements PrintJobAttribute{}
public class LinkedHashSet<E>extends HashSet<E>implements Set<E>, Cloneable, Serializable{}
public class TreeSet<E>extends AbstractSet<E>implements SortedSet<E>, Cloneable, Serializable{}
能够看出,能够实例化的类为:CopyOnWriteArraySet,HashSet,LinkedHashSet,TreeSet。
二、Set是如何实现元素惟一性的
javadoc中对Set的描述第一段以下:“A collection that contains no duplicate elements. More formally, sets contain no pair of elements e1
and e2 such that e1.equals(e2), and at most one null element. As implied by its name, this interface models the mathematical set abstraction.”
这段话是对是错,请看下面分析。
要进行下面的论述,咱们先了解一下Map。Map中的元素是“键-值”对,其中“键”必须是惟一的。TreeSet和HashSet就是利用这个特性实现“no duplicate elements”。它把set中的元素做为Map中的“键”,从而保持元素的惟一性。这些键在Map中又是如何区分的呢?不一样的Map有不一样的作法,并且区别很大。
下面咱们分别就TreeSet、HashSet和CopyOnWriteArraySet进行论述:
2.一、TreeSet部分:
如下以TreeSet为例进行分析。
请看TreeSet的部分实体:
public class TreeSet<E> extends AbstractSet<E>
implements SortedSet<E>, Cloneable, java.io.Serializable
{
// The backing Map
private transient SortedMap<E,Object> m;
// The keySet view of the backing Map
private transient Set<E> keySet;
// Dummy value to associate with an Object in the backing Map
//这是每一个键所指的对像
private static final Object PRESENT = new Object();
//constructor
private TreeSet(SortedMap<E,Object> m) {
this.m = m;
keySet = m.keySet();
}
public TreeSet() {
this(new TreeMap<E,Object>());
}
//如下省略..........
}
能够看到TreeSet使用了SortedMap做为其Map保存“键-值”对,而这个SortedMap的真正实体是TreeMap。
请看示例程序1:
import java.util.*;
public class SetTest1 {
public static void main(String[] args){
Set set = new TreeSet();
set.add(new SetElement1("aa"));
set.add(new SetElement1("bb"));
}
static class SetElement1{
String s;
public SetElement1(String s){
this.s = s;
}
public String toString(){
return s;
}
public boolean equals(Object obj) {
return s.equals(((SetElement1)obj).s);
}
}
}
该程序可以正常编译,可是运行时会抛出异常java.lang.ClassCastException。为何?
请看示例程序2:
import java.util.*;
public class SetTest2 {
public static void main(String[] args){
Set set = new TreeSet();
set.add(new SetElement2("aa"));
set.add(new SetElement2("aa"));
set.add(new SetElement2("bb"));
System.out.println(set);
}
static class SetElement2 implements Comparable{
String s;
public SetElement2(String s){
this.s = s;
}
public String toString(){
return s;
}
public int compareTo(Object o){
return s.compareTo(((SetElement2)o).s);
}
public boolean equals(Object obj) {
return s.equals(((SetElement2)obj).s);
}
}
}
运行结果:
[aa, bb]
这正是咱们所指望的结果。那“示例程序1”和“示例程序2”有什么区别?
是由于SetElement2实现了Comparable接口,而SetElement1没有。SetElement2实现Comparable接口有什么用呢?由于在TreeSet的add方法中须要比较两个 元素的“值”。请看TreeMap中的compare方法:
private int compare(K k1, K k2) {
return (comparator==null ? ((Comparable</*-*/K>)k1).compareTo(k2)
: comparator.compare((K)k1, (K)k2));
}
可见这个方法先把要比较的元素down cast成Comparable类型。这里就能够解释“示例程序1”中为何会抛出异常java.lang.ClassCastException,因SetElement1没有实现Comparable接口,固然就不能down cast成Comparable。可见,要用TreeSet来作为你的Set,那么Set中所装的元素都必须实现了Comparable接口。
说到这里,你是否是想到了TreeSet中是采用Comparable接口中的compareTo方法来判断元素是否相同(duplicate),而不是采用其余相似equals之类的东东来判断。
请看示例程序3:
import java.util.Set;
import java.util.*;
public class SetTest3 {
public static void main(String[] args){
Set set = new HashSet();
set.add(new SetElement3("aa"));
set.add(new SetElement3("aa"));
set.add(new SetElement3("bb"));
System.out.println(set);
}
static class SetElement3 implements Comparable{
String s;
public SetElement3(String s){
this.s = s;
}
public String toString(){
return s;
}
public int compareTo(Object o){
//return s.compareTo(((SetElement3)o).s);
return -1;
}
public boolean equals(Object obj) {
return s.equals(((SetElement3)obj).s);
}
}
}
运行结果:
[bb, aa, aa]
看到没有,有两个“aa”!!这是由于compareTo返回值始终是"-1",也就是说“把任何元素都当作不一样”。
综上所述,你是否对javadoc中对Set功能的描述有了怀疑?!
2.二、HashSet部分:
如下以HashSet为例进行分析。
从Hashset类的主体部分:
public class HashSet<E> extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
{
static final long serialVersionUID = -5024744406713321676L;
private transient HashMap<E,Object> map;
// Dummy value to associate with an Object in the backing Map
//这是每一个键所指的对像
private static final Object PRESENT = new Object();
public HashSet() {
map = new HashMap<E,Object>();
}
public boolean add(E o) {
return map.put(o, PRESENT)==null;
}
//如下省略..........
}
public HashSet() {
map = new HashMap<E,Object>();
}
能够看到HashSet使用了HashMap做为其Map保存“键-值”对。
请看示例程序4:
import java.util.*;
public class SetTest4 {
public static void main(String[] args){
Set set = new HashSet();
set.add(new SetElement4("aa"));
set.add(new SetElement4("aa"));
set.add(new SetElement4("bb"));
System.out.println(set);
}
static class SetElement4{
String s;
public SetElement4(String s){
this.s = s;
}
public String toString(){
return s;
}
public boolean equals(Object obj) {
return s.equals(((SetElement4)obj).s);
}
}
}
运行结果:
[bb, aa, aa]
没有“示例程序1”中的java.lang.ClassCastException,可是运行结果彷佛不对,由于有两个“aa”。
请看示例程序5:
import java.util.*;
public class SetTest5 {
public static void main(String[] args){
Set set = new HashSet();
set.add(new SetElement5("aa"));
set.add(new SetElement5("aa"));
set.add(new SetElement5("bb"));
System.out.println(set);
}
static class SetElement5{
String s;
public SetElement5(String s){
this.s = s;
}
public String toString(){
return s;
}
public boolean equals(Object obj) {
return s.equals(((SetElement5)obj).s);
}
public int hashCode() {
//return super.hashCode();
return s.hashCode();
}
}
}
运行结果:
[bb, aa]
这就对了。“示例程序4”和“示例程序5”有什么区别?是SetElement5重写了hashCode方法。
可见HashSet中是采用了比较元素hashCode的方法来判断元素是否相同(duplicate),而不是采用其余相似equals之类的东东来判断。
说了这么多,那java类库中到底有没有根据equals来判断元素是否相同(duplicate)的Set呢?请看下文。
2.二、CopyOnWriteArraySet部分:
类CopyOnWriteArraySet是java.util.concurrent包中的一个类,因此它是线程安全的。
CopyOnWriteArraySet是使用CopyOnWriteArrayList做为其盛放元素的容器。当往CopyOnWriteArrayList添加新元素,它都要遍历整个List,而且用equals来 比较两个元素是否相同。
请看示例程序6:
import java.util.*;
import java.util.concurrent.*;
public class SetTest6 {
public static void main(String[] args){
Set set = new CopyOnWriteArraySet();
set.add(new SetElement6("aa"));
set.add(new SetElement6("aa"));
set.add(new SetElement6("bb"));
System.out.println(set);
}
static class SetElement6{
String s;
public SetElement6(String s){
this.s = s;
}
public String toString(){
return s;
}
public boolean equals(Object obj) {
return s.equals(((SetElement6)obj).s);
}
}
}
运行结果:
[aa, bb]
好了,一切搞定!!
三、总结:
Javadoc中的一些描述多是不许确的,你们要小心了!
Set中实现元素互异的各类方法差别很大,大体能够分为三种:使用equals,使用hashCode,使用compareTo。可是我尚未发现采用“判断地址空间是否相同”来判断元素是否相同的类,固然咱们能够用现有的三种方法来实现“判断地址空间是否相同”。
综上所述,咱们能够总结出使用Set的三种不一样的情形:(如下假设元素类为Element)
A、若是想使用Element的equals方法来判断元素是否相同,那么可使用CopyOnWriteArraySet来构造类的实体。
B、若是Element实现了Comparable接口,并且想使用compareTo方法来判断元素是否相同,那么可使用TreeSet来构造类的实体。
C、若是想使用判断hashCode是否相同的方法来判断元素是否相同,那么可使用HashSet来构造类的实体java