Set是一個不能包含重複元素的Collection,它模擬了數學集抽象,Set
接口僅包含從Collection
繼承的方法,並添加禁止重複元素的限制,Set
還爲equals
和hashCode
操做的行爲添加了一個更強的契約,容許Set
實例有意義地進行比較,即便它們的實現類型不一樣,若是兩個Set
實例包含相同的元素,則它們是相等的。html
Java平臺包含三個通用的Set實現:HashSet
、TreeSet
和LinkedHashSet
。將其元素存儲在哈希表中的HashSet是性能最佳的實現,但它不能保證迭代的順序。TreeSet將其元素存儲在紅黑樹中,根據元素的值對其元素進行排序,它比HashSet
慢得多。LinkedHashSet實現爲哈希表,其中有一個鏈表,根據它們插入集合的順序(插入順序)對其元素進行排序,LinkedHashSet
讓它的客戶端避免了HashSet
提供的未指定的、一般混亂的排序,但代價只稍微高一點。java
這是一個簡單但有用的Set
語法,假設你有一個Collection
,c
,而且你想要建立另外一個包含相同元素的Collection
,但會刪除全部重複項,下面的一行代碼就能夠解決這個問題。git
Collection<Type> noDups = new HashSet<Type>(c);
它的工做原理是建立一個Set
(根據定義,它不能包含重複項),初始化包含c
中的全部元素,它使用Collection接口部分中描述的標準轉換構造函數。github
或者,若是使用JDK 8或更高版本,你能夠使用聚合操做輕鬆收集到Set
:編程
c.stream() .collect(Collectors.toSet()); // no duplicates
這是一個稍長的示例,它將名稱Collection
累積到TreeSet
中:segmentfault
Set<String> set = people.stream() .map(Person::getName) .collect(Collectors.toCollection(TreeSet::new));
如下是第一個語法的次要變體,它在刪除重複元素時保留了原始集合的順序:api
Collection<Type> noDups = new LinkedHashSet<Type>(c);
如下是封裝前面的語法的泛型方法,返回與傳遞的相同的泛型類型的Set
。數組
public static <E> Set<E> removeDups(Collection<E> c) { return new LinkedHashSet<E>(c); }
size
操做返回Set
中的元素數(其基數),isEmpty
方法徹底符合你的想法,add
方法將指定的元素添加到Set
(若是它尚不存在)並返回一個布爾值,指示是否添加了元素。相似地,remove
方法從Set
中刪除指定的元素(若是存在)並返回一個布爾值,指示元素是否存在,iterator
方法在Set
上返回Iterator
。oracle
如下程序打印出其參數列表中的全部不一樣單詞,提供了該程序的兩個版本,第一個使用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
,應用時,它們執行標準的集代數運算,假設s1
和s2
是Set
,批量操做是這樣作的:
s1.containsAll(s2
) — 若是s2
是s1
的子集,則返回true
(若是set
s1
包含s2
中的全部元素,則s2
是s1
的子集)。s1.addAll(s2)
— 將s1
轉換爲s1
和s2
的並集(兩個集合的並集是包含任一集合中包含的全部元素的集合)。s1.retainAll(s2)
— 將s1
轉換爲s1
和s2
的交集(兩個集合的交集是僅包含兩個集合共有的元素的集合)。s1.removeAll(s2)
— 將s1
轉換爲s1
和s2
的(非對稱)差集(例如,s1
減s2
的差集就是包含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);
除了對其餘任何Collection
執行的操做以外,數組操做不會對Set
執行任何特殊操做,Collection接口部分介紹了這些操做。