Set 继承关系
Set 直接继承了 Collection 接口,也是在 JDK 1.2 提供的。Set的结构及实现都和Map保持高度一致,通过后面的源码分析你就可以看出来 Set 内部大量使用了 Map 的实现作为数据存储。
类文档解读
老规矩,先通过 Set 的类文档了解一下能得到哪些信息。
- Set 是一个不允许重复的集合,Set 是数学上的集合的抽象,更正式地说,集合不包含一对元素 e1 和 e2 满足 e1.equals(e2)。
- Set 最多只能包含一个 null 元素或者不允许 null 元素,具体要取决于实现类的具体实现。
Set API
public interface Set<E> extends Collection<E> {
// Query Operations
/**
* 返回集合中元素的数量,如果元素数量超过 Integer.MAX_VALUE,则返回 Integer.MAX_VALUE
*/
int size();
/**
* 集合中元素数量是为0
*/
boolean isEmpty();
/**
* 如果集合中包含指定的元素则返回 true,如果集合中包含 null 元素并且要查找的元素也是 null 的时候也返回 true。
* @throws 如果指定的元素与集合元素类型不兼容则抛出 ClassCastException
* @throws 如果集合不允许 null 元素且指定的元素是 null 则抛出 NullPointerException
*/
boolean contains(Object o);
/**
* 返回此集合中元素的迭代器。对于元素返回的顺序没有任何保证(除非此集合是某个提供保证的类的实例)。
*/
Iterator<E> iterator();
/**
*同 Collection
*/
Object[] toArray();
/**
* 同 Collection
*/
<T> T[] toArray(T[] a);
// Modification Operations
/**
* 如果指定的元素不存在,则将其添加到此集合。
* 如果此集合已包含元素,则保持集合不变并返回 false。这可以确保集合永远不包含重复的元素。
* 上面的规定并不意味着集合必须接受所有元素;集合可以拒绝添加任何特定元素,包括 null,并抛出异常。
*
* @throws 不支持此操作抛出 UnsupportedOperationException
* @throws 指定的元素与当前 Set 中的元素类型不匹配抛出 ClassCastException
* @throws 指定的元素是 null 且当前 Set 不支持 null 元素抛出 NullPointerException
* @throws 如果指定元素无法添加到当前 Set 抛出 IllegalArgumentException
*/
boolean add(E e);
/**
* 如果指定的元素存在,则将其从该集中移除且返回 true。
*
* @throws 指定的元素与当前 Set 中的元素类型不匹配抛出 ClassCastException
* @throws 指定的元素是 null 且当前 Set 不支持 null 元素抛出 NullPointerException
* @throws 不支持此操作抛出 UnsupportedOperationException
*/
boolean remove(Object o);
// Bulk Operations
/**
* 如果此集合包含指定集合的所有元素,则返回true。如果指定的集合也是一个 Set,
* 如果指定的集合是当前集合的子集则返回 true。
*
* @throws 指定的元素与当前 Set 中的元素类型不匹配抛出 ClassCastException
* @throws 指定的元素是 null 且当前 Set 不支持 null 元素抛出 NullPointerException
*/
boolean containsAll(Collection<?> c);
/**
* 将指定集合中的所有元素添加到此集中,如果指定的集合也是 Set 则是一个并集操作
*
* @throws 不支持此操作抛出 UnsupportedOperationException
* @throws 指定的元素与当前 Set 中的元素类型不匹配抛出 ClassCastException
* @throws 指定的元素是 null 且当前 Set 不支持 null 元素抛出 NullPointerException
* @throws 如果指定元素无法添加到当前 Set 抛出 IllegalArgumentException
*/
boolean addAll(Collection<? extends E> c);
/**
* 只保留指定集合不存在的元素,如果指定集合也是 Set 则是交集操作
*
* @throws 不支持此操作抛出 UnsupportedOperationException
* @throws 指定的元素与当前 Set 中的元素类型不匹配抛出 ClassCastException
* @throws 指定的元素是 null 且当前 Set 不支持 null 元素抛出 NullPointerException
*/
boolean retainAll(Collection<?> c);
/**
* 从此集中移除指定集合中包含的所有元素,如果指定集合也是 Set 则是差集操作
*
* @throws 不支持此操作抛出 UnsupportedOperationException
* @throws 指定的元素与当前 Set 中的元素类型不匹配抛出 ClassCastException
* @throws 指定的元素是 null 且当前 Set 不支持 null 元素抛出 NullPointerException
*/
boolean removeAll(Collection<?> c);
/**
* 清空 Set
*
* @throws 不支持此操作抛出 UnsupportedOperationException
*/
void clear();
// Comparison and hashing
/**
* 将指定的对象与此集进行相等性比较。如果指定的对象也是一个 Set,两个集合的大小相同,
* 并且指定集合的每个成员都包含在该集合中,则返回true。
*/
boolean equals(Object o);
/**
* 返回此集合的哈希码。集合的哈希码为集合中元素的哈希码之和,其中空元素的哈希码为 0。
* 这确保了 s1.equals(s2) 意味着 s1.hashCode() == s2.hashCode()
* @see Object#equals(Object)
* @see Set#equals(Object)
*/
int hashCode();
@Override
default Spliterator<E> spliterator() {
return Spliterators.spliterator(this, Spliterator.DISTINCT);
}
}
Set 是如何判断两个元素是否相等的
Set 集合判断两个元素相等的标准:两个对象通过 hashCode() 方法比较相等,并且两个对象的 equals() 方法返回值也相等。建议自定义类要重写hashCode() 方法和 equals() 方法。
首先 Set 集合是通过哈希算法来存储元素的,当像 Set 中添加对象时,首先调用此对象所在类的 hashCode() 方法,计算此对象的哈希值,此哈希值决定了此对象在 Set 中存储的位置。若此位置没有对象存储,则直接把对象存储进来,如果此位置已经有一个对象了,则通过 equals() 方法比较这两个对象是否相同,如果不同则存储进去,如果相同则这个对象不能存储进 Set 里。所以我们重写 hashCode() 方法和 equals() 方法,还要保持这两个方法一致,即 hashCode 相同的对象 equals 也要相同。
重写 hashCode() 方法的基本原则
- 在程序运行时,同一个对象多次调用 hashCode() 方法应该返回相同的值
- 当两个对象的 equals() 方法比较返回 true 时,这两个对象的 hashCode() 方法的返回值也应为 true
- 对象中用作 equals() 方法比较的 Field,都应该用来计算 hashCode 值
AbstractSet 继承关系
AbstractSet 是 JDK 1.2 提供的抽象类,它的作用和 AbstractCollection 一样都是对接口提供一个通用实现供子类使用。只是此类的子类中的所有方法和构造函数都必须遵守 Set 接口的附加约束(如 add 方法不允许添加多个相等对象)。AbstractSet 不重写 AbstractCollection 类中的任何实现。仅仅为它添加了 hashCode 和 equals 的实现。
AbstractSet API
public abstract class AbstractSet<E> extends AbstractCollection<E> implements Set<E> {
protected AbstractSet() {
}
// Comparison and hashing
/**
* 内部通过 containsAll 方法判断
*/
public boolean equals(Object o) {
// 首先判断指定的对象是不是当前 Set
if (o == this)
return true;
// 如果不是 Set 则返回 false
if (!(o instanceof Set))
return false;
Collection<?> c = (Collection<?>) o;
// 数量不同返回 false
if (c.size() != size())
return false;
try {
// 通过 containsAll 方法判断
return containsAll(c);
} catch (ClassCastException unused) {
return false;
} catch (NullPointerException unused) {
return false;
}
}
/**
* 通过迭代器将所有元素的哈希码相加计算得到 Set 的哈希码
*/
public int hashCode() {
int h = 0;
Iterator<E> i = iterator();
while (i.hasNext()) {
E obj = i.next();
if (obj != null)
h += obj.hashCode();
}
return h;
}
/**
* 先找到数量最小的集合,以此集合作为基础进行迭代删除
*
* @throws 不支持此操作抛出 UnsupportedOperationException
* @throws 指定的元素与当前 Set 中的元素类型不匹配抛出 ClassCastException
* @throws 指定的元素是 null 且当前 Set 不支持 null 元素抛出 NullPointerException
*/
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
boolean modified = false;
if (size() > c.size()) {
for (Iterator<?> i = c.iterator(); i.hasNext(); )
modified |= remove(i.next());
} else {
for (Iterator<?> i = iterator(); i.hasNext(); ) {
if (c.contains(i.next())) {
i.remove();
modified = true;
}
}
}
return modified;
}
}