1. 程式人生 > >Java基礎系列(四十六):Set & AbstractSet

Java基礎系列(四十六):Set & AbstractSet

Set

Set繼承了Collection介面,它本身也是一個介面,代表一種不能擁有重複元素的容器型別,更確切的說,集合不包含一對元素e1e2 ,使得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中提供了三個方法的重寫,分別是equalshashCoderemoveAll這三個方法,首先我們來看一下equalshashCode是如何重寫的。

/**
 * 比較指定物件與此 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中對於equalshashCode方法的要求。

下面還有一個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)。

原創文章,文筆有限,才疏學淺,文中若有不正之處,萬望告知。


公眾號

在這裡插入圖片描述