Java基礎系列(四十六):Set & AbstractSet
Set
Set
繼承了Collection
介面,它本身也是一個介面,代表一種不能擁有重複元素的容器型別,更確切的說,集合不包含一對元素e1
和e2
,使得e1.equals(e2)
。
通過Set
的一些實現,我們可以發現,Set
是基於Map
進行實現的,所以Set
取值時不保證資料和存入的時候順序一致,並且不允許空值,不允許重複值。下面我們來看一下Set
都給我們提供了哪些方法。
方法
首先,Set
提供一些關於本身屬性的介面:
/**
* 返回 set 中的元素個數
* @return set中元素個數
*/
int size();
/**
* 如果set中不包含任何元素,返回true
* @return 如果set中不包含任何元素,返回true
*/
boolean isEmpty();
當然,也提供了去該集合中查詢元素是否存在的介面:
/**
* 如果set包含指定的元素,則返回 true
* @param o 指定的元素
* @return 如果 set 包含指定的元素,則返回 true。
*/
boolean contains(Object o);
/**
* 如果此 set 包含指定 collection 的所有元素,則返回 true。
* 如果指定的 collection 也是一個 set,那麼當該 collection 是此 set 的 子集 時返回 true。
* @param c 檢查是否包含在此 set 中的 collection
* @return 如果此 set 包含指定 collection 中的所有元素,則返回 true
*/
boolean containsAll(Collection<?> c);
對於元素進行結構性操作的介面也有幾個,這裡需要注意的是,在新增元素的時候,如果該元素在集合中已經存在,會導致新增失敗並返回一個false。
/**
* 如果 set 中尚未存在指定的元素,則新增此元素
* @param e 被新增的元素
* @return 如果set中存在該元素,新增失敗並返回false
*/
boolean add(E e);
/**
* 如果 set 中沒有指定 collection 中的所有元素,則將其新增到此 set 中
* 如果指定的 collection 也是一個 set,則 addAll 操作會實際修改此 set,
* 這樣其值是兩個 set 的一個 並集。如果操作正在進行的同時修改了指定的 collection,則此操作的行為是不確定的。
* @param c
* @return
*/
boolean addAll(Collection<? extends E> c);
/**
* 如果 set 中存在指定的元素,則將其移除(可選操作)。
* @param o 被刪除的元素
* @return 如果此 set 包含指定的物件,則返回true
*/
boolean remove(Object o);
/**
* 僅保留 set 中那些包含在指定 collection 中的元素,換句話說,只取兩者交集,其餘的不管
* @param c 與set進行判斷的集合
* @return 如果此 set 由於呼叫而發生更改,則返回 true
*/
boolean retainAll(Collection<?> c);
/**
* 移除 set 中那些包含在指定 collection 中的元素,也就是說,取交集之外的所有元素
* @param c 與set進行判斷的集合
* @return 如果此 set 由於呼叫而發生更改,則返回 true
*/
boolean removeAll(Collection<?> c);
/**
* 移除此 set 中的所有元素,此呼叫返回後該 set 將是空的。
*/
void clear();
Set
中提供了一個預設的獲取可切割迭代器的一個例項,是通過Spliterators
方法進行獲取
/**
* 可切割的迭代器,返回的是該set集合的可切割迭代器的一個例項
* @return
*/
@Override
default Spliterator<E> spliterator() {
return Spliterators.spliterator(this, Spliterator.DISTINCT);
}
Set
的方法到這裡就告一段落了,可以看出其中的方法與Collection
相比並沒有特別大的區別,下面我們來看看作為抽象實現類AbstractSet
中給我們提供了哪些基礎的實現。
AbstractSet
通過原始碼我們可以看到,AbstractSet
中提供了三個方法的重寫,分別是equals
,hashCode
,removeAll
這三個方法,首先我們來看一下equals
和hashCode
是如何重寫的。
/**
* 比較指定物件與此 set 的相等性。如果給定物件也是一個 set,
* 兩個 set 的大小相等,並且給定 set 的每個成員都包含在此 set 中,則返回 true。
* 這確保 equals 方法在 Set 介面的不同實現間正常工作。
* @param o 被比較的元素
* @return 如果相等返回true
*/
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof Set)) {
return false;
}
Collection<?> c = (Collection<?>) o;
if (c.size() != size()) {
return false;
}
try {
return containsAll(c);
} catch (ClassCastException unused) {
return false;
} catch (NullPointerException unused) {
return false;
}
}
/**
* 返回此 set 的雜湊碼值。set 的雜湊碼被定義為該 set 中元素的雜湊碼的總和,其中 null 元素的雜湊碼被定義為 0。
* 這確保了 s1.equals(s2) 意味著對於任何兩個 set s1 和 s2,都有 s1.hashCode()==s2.hashCode()。
* @return
*/
@Override
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;
}
可以看出,equals
方法保證了呼叫該方法的兩個物件必須是實現了Set
介面的,而且具有一些的容錯性,即Set
的不同子類之間也可以使用equals
方法來判斷兩個物件是否相等,而hashCode
方法的計算方式則是利用了迭代器,將每一項不為null的元素的雜湊值相加而得到的,這樣就可以保證了對於任意的兩個物件,他們的雜湊值都是相等的,這和equals
方法相匹配,符合了Object
中對於equals
和hashCode
方法的要求。
下面還有一個removeAll
方法,我們一起來看看
/**
* 從此 set 中移除包含在指定 collection 中的所有元素
* 如果指定 collection 也是一個 set,則此操作有效地修改此 set,從而其值成為兩個 set 的 不對稱差集。
*
* @param c 包含將從此 set 中移除的元素的 collection
* @return 如果此 set 由於呼叫而發生更改,則返回 true
*/
@Override
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
boolean modified = false;
//通過在此 set 和指定 collection 上呼叫 size 方法,此實現可以確定哪一個更小。
if (size() > c.size()) {
// 如果此 set 中的元素更少,則該實現將在此 set 上進行迭代,依次檢查迭代器返回的每個元素,檢視它是否包含在指定的 collection 中。
for (Iterator<?> i = c.iterator(); i.hasNext(); ) {
//如果包含它,則使用迭代器的 remove 方法從此 set 中將其移除。
modified |= remove(i.next());
}
} else {
//如果指定 collection 中的元素更少,則該實現將在指定的 collection 上進行迭代,並使用此 set 的 remove 方法,從此 set 中移除迭代器返回的每個元素。
for (Iterator<?> i = iterator(); i.hasNext(); ) {
if (c.contains(i.next())) {
i.remove();
modified = true;
}
}
}
return modified;
}
可以看出,這個方法是使用了迭代器進行完成的,這裡有些不太理解,為什麼僅僅實現了這一個方法。或者說,為什麼要在這裡實現這個方法。希望知道的朋友可以告訴我~(微訊號:cm_950825)。
原創文章,文筆有限,才疏學淺,文中若有不正之處,萬望告知。