Java™ 教程(Set介面)

博弈發表於2019-01-19

Set介面

Set是一個不能包含重複元素的Collection,它模擬了數學集抽象,Set介面僅包含從Collection繼承的方法,並新增禁止重複元素的限制,Set還為equalshashCode操作的行為新增了一個更強的契約,允許Set例項有意義地進行比較,即使它們的實現型別不同,如果兩個Set例項包含相同的元素,則它們是相等的。

Java平臺包含三個通用的Set實現:HashSetTreeSetLinkedHashSet。將其元素儲存在雜湊表中的HashSet是效能最佳的實現,但它不能保證迭代的順序。TreeSet將其元素儲存在紅黑樹中,根據元素的值對其元素進行排序,它比HashSet慢得多。LinkedHashSet實現為雜湊表,其中有一個連結串列,根據它們插入集合的順序(插入順序)對其元素進行排序,LinkedHashSet讓它的客戶端避免了HashSet提供的未指定的、通常混亂的排序,但代價只稍微高一點。

這是一個簡單但有用的Set語法,假設你有一個Collectionc,並且你想要建立另一個包含相同元素的Collection,但會刪除所有重複項,下面的一行程式碼就可以解決這個問題。

Collection<Type> noDups = new HashSet<Type>(c);

它的工作原理是建立一個Set(根據定義,它不能包含重複項),初始化包含c中的所有元素,它使用Collection介面部分中描述的標準轉換建構函式。

或者,如果使用JDK 8或更高版本,你可以使用聚合操作輕鬆收集到Set

c.stream()
.collect(Collectors.toSet()); // no duplicates

這是一個稍長的示例,它將名稱Collection累積到TreeSet中:

Set<String> set = people.stream()
.map(Person::getName)
.collect(Collectors.toCollection(TreeSet::new));

以下是第一個語法的次要變體,它在刪除重複元素時保留了原始集合的順序:

Collection<Type> noDups = new LinkedHashSet<Type>(c);

以下是封裝前面的語法的泛型方法,返回與傳遞的相同的泛型型別的Set

public static <E> Set<E> removeDups(Collection<E> c) {
    return new LinkedHashSet<E>(c);
}

Set介面基礎操作

size操作返回Set中的元素數(其基數),isEmpty方法完全符合你的想法,add方法將指定的元素新增到Set(如果它尚不存在)並返回一個布林值,指示是否新增了元素。類似地,remove方法從Set中刪除指定的元素(如果存在)並返回一個布林值,指示元素是否存在,iterator方法在Set上返回Iterator

以下程式列印出其引數列表中的所有不同單詞,提供了該程式的兩個版本,第一個使用JDK 8聚合操作,第二個使用for-each構造。

使用JDK 8聚合操作:

import java.util.*;
import java.util.stream.*;

public class FindDups {
    public static void main(String[] args) {
        Set<String> distinctWords = Arrays.asList(args).stream()
        .collect(Collectors.toSet()); 
        System.out.println(distinctWords.size()+ 
                           " distinct words: " + 
                           distinctWords);
    }
}

使用for-each構造:

import java.util.*;

public class FindDups {
    public static void main(String[] args) {
        Set<String> s = new HashSet<String>();
        for (String a : args)
               s.add(a);
               System.out.println(s.size() + " distinct words: " + s);
    }
}

現在執行該程式的任一版本。

java FindDups i came i saw i left

生成以下輸出:

4 distinct words: [left, came, saw, i]

請注意,程式碼始終引用Collection通過其介面型別(Set)而不是其實現型別,這是一個強烈推薦的程式設計實踐,因為它使你可以靈活地僅通過更改建構函式來更改實現。如果用於儲存集合的變數或用於傳遞它的引數中的任何一個被宣告為Collection的實現型別而不是其介面型別,必須更改所有這些變數和引數才能更改其實現型別。

此外,無法保證生成的程式能夠正常執行,如果程式使用原始實現型別中存在但未在新實現型別中存在的任何非標準操作,則程式將失敗,僅通過其介面引用集合可防止你使用任何非標準操作。

前面示例中Set的實現型別是HashSet,它不保證Set中元素的順序,如果你希望程式按字母順序列印單詞列表,只需將Set的實現型別從HashSet更改為TreeSet,進行這個簡單的單行更改會導致前一個示例中的命令列生成以下輸出。

java FindDups i came i saw i left

4 distinct words: [came, i, left, saw]

Set介面批量操作

批量操作特別適合於Set,應用時,它們執行標準的集代數運算,假設s1s2Set,批量操作是這樣做的:

  • s1.containsAll(s2) — 如果s2s1的子集,則返回true(如果set s1包含s2中的所有元素,則s2s1的子集)。
  • s1.addAll(s2) — 將s1轉換為s1s2的並集(兩個集合的並集是包含任一集合中包含的所有元素的集合)。
  • s1.retainAll(s2) — 將s1轉換為s1s2的交集(兩個集合的交集是僅包含兩個集合共有的元素的集合)。
  • s1.removeAll(s2) — 將s1轉換為s1s2的(非對稱)差集(例如,s1s2的差集就是包含s1中所有元素但不包含s2中的所有元素的集)。

若要非破壞性地計算兩個集合的並集、交集或差集(不修改任何一個集合),呼叫者必須在呼叫適當的批量操作之前複製一個集合,以下是由此產生的語法。

Set<Type> union = new HashSet<Type>(s1);
union.addAll(s2);

Set<Type> intersection = new HashSet<Type>(s1);
intersection.retainAll(s2);

Set<Type> difference = new HashSet<Type>(s1);
difference.removeAll(s2);

前面的語法中的結果集的實現型別是HashSet,如前所述,它是Java平臺中最好的全能Set實現,但是,任何通用的Set實現都可以替代。

讓我們重溫一下FindDups程式,假設你想知道引數列表中的哪些單詞只出現一次,哪些單詞出現多次,但你不希望重複列印出任何重複項,這種效果可以通過生成兩個集合來實現 — 一個集合包含引數列表中的每個單詞,另一個集合僅包含重複項。僅出現一次的單詞是這兩組的差集,我們知道如何計算,以下是生成的程式的樣子。

import java.util.*;

public class FindDups2 {
    public static void main(String[] args) {
        Set<String> uniques = new HashSet<String>();
        Set<String> dups    = new HashSet<String>();

        for (String a : args)
            if (!uniques.add(a))
                dups.add(a);

        // Destructive set-difference
        uniques.removeAll(dups);

        System.out.println("Unique words:    " + uniques);
        System.out.println("Duplicate words: " + dups);
    }
}

當使用前面使用的相同引數列表執行時(i came i saw i left),程式產生以下輸出。

Unique words:    [left, saw, came]
Duplicate words: [i]

不太常見的集代數運算是對稱差集 — 包含在兩個指定集合中但不同時包含在兩個集合中的元素的集合,以下程式碼非破壞性地計算兩個集合的對稱差集。

Set<Type> symmetricDiff = new HashSet<Type>(s1);
symmetricDiff.addAll(s2);
Set<Type> tmp = new HashSet<Type>(s1);
tmp.retainAll(s2);
symmetricDiff.removeAll(tmp);

Set介面陣列操作

除了對其他任何Collection執行的操作之外,陣列操作不會對Set執行任何特殊操作,Collection介面部分介紹了這些操作。


上一篇:Collection介面

相關文章