Функциональность Set
Set имеет точно такой же интерфейс, как и Collection, но здесь нет дополнительной функциональности, как и в случае двух других списков. Вместо этого Set - это точно Collection, он имеет отличающееся поведение. (Это идеальное использование наследования и полиморфизма: для выражения разницы в поведении.) Set отклоняет хранение нескольких экземпляров одного значения объекта (что понимается под “значением” объекта - это более сложный вопрос, как вы скоро увидите).
Set (интерфейс) | Каждый элемент, который вы добавляете в Set, должен быть уникальным; в противном случае Set не добавит дублирующий элемент. Object, добавляемый в Set, должен определить equals( ) для установления уникальности объектов. Set имеет точно такой же интерфейс, что и Collection. Интерфейс Set не гарантирует сохранение порядка следования элементов в определенной последовательности. |
HashSet* | Для Set, в которых важно время поиска. Object должен определить hashCode( ). |
TreeSet | Упорядоченный Set поддерживаемый деревом. Этим способом вы можете получить упорядоченную последовательность из Set. |
Приводимый пример не показывает всего, что вы можете делать с Set, так как его интерфейс тот же, что и у Collection, который был использован в предыдущих примерах. Вместо этого демонстрируется поведение, которое делает Set уникальным:
//: c09:Set1.java
// То, что вы можете сделать с Set.
import java.util.*; import com.bruceeckel.util.*;
public class Set1 { static Collections2.StringGenerator gen = Collections2.countries; public static void testVisual(Set a) { Collections2.fill(a, gen.reset(), 10); Collections2.fill(a, gen.reset(), 10); Collections2.fill(a, gen.reset(), 10); System.out.println(a); // Без дублирования!
// Добавление другого набора в этот:
a.addAll(a); a.add("one"); a.add("one"); a.add("one"); System.out.println(a); // Просмотр:
System.out.println("a.contains(\"one\"): " + a.contains("one")); } public static void main(String[] args) { System.out.println("HashSet"); testVisual(new HashSet()); System.out.println("TreeSet"); testVisual(new TreeSet()); } } ///:~
Дублирующиеся значения добавляются в Set, но при распечатке вы увидите, что Set принял только один экземпляр каждого значения.
Когда вы запустите программу, вы заметите, что порядок, содержащийся в HashSet, отличается от TreeSet, так как каждый из них имеет различные способы сортировки элементов, так чтобы они могли быть найдены позднее. (TreeSet хранит их отсортированными, а HashSet использует функцию хеширования, которая предназначена специально для многократного поиска.) Когда создаете свои собственные типы, запомните, что для Set необходим способ обработки порядка сортировки, что означает, что вы должны реализовать интерфейс Comparable и определить метод compareTo( ). Вот пример:
//: c09:Set2.java
// Помещение своего типа в Set.
import java.util.*;
class MyType implements Comparable { private int i; public MyType(int n) { i = n; } public boolean equals(Object o) { return (o instanceof MyType) && (i == ((MyType)o).i); } public int hashCode() { return i; } public String toString() { return i + " "; } public int compareTo(Object o) { int i2 = ((MyType)o).i; return (i2 < i ? -1 : (i2 == i ? 0 : 1)); } }
public class Set2 { public static Set fill(Set a, int size) { for(int i = 0; i < size; i++) a.add(new MyType(i)); return a; } public static void test(Set a) { fill(a, 10); fill(a, 10); // Попытка создать дублирование
fill(a, 10); a.addAll(fill(new TreeSet(), 10)); System.out.println(a); } public static void main(String[] args) { test(new HashSet()); test(new TreeSet()); } } ///:~
Форма определения для equals( ) и hashCode( ) будет описана позднее в этой главе. Вы должны определить equals( ) в обоих классах, а hashCode( ) абсолютно необходима только если класс будет помещен в HashSet (что предпочтительнее, так как он должен быть выбран вами в первую очередь в качестве реализации Set). Однако, в качестве стиля программирования, вы должны всегда перегружать hashCode( ), когда вы перегружаете equals( ). Этот процесс будет позднее детализирован в этой главе.
Обратите внимание, что в методе compareTo( ) я не использую “простую и очевидную” форму return i-i2. Это общая ошибка программистов, это будет работать правильно, если i и i2 являются “беззнаковыми” целыми (если бы Java имел ключевое слово “unsigned”, но это не так). Это неправильно для отрицательных знаковых int в Java, который не достаточно велик, чтобы представить разность между двумя знаковыми int. Если i - это большое положительное целое, а j - это большое отрицательное целое, то при i-j будет переполнение и возвратится отрицательное значение, и это не будет работать.