Към съдържанието

Тема 6 — Колекции (Collections Framework)

Преди Java 5.0 всеки контейнер работеше с Object референции — програмистът ръчно привеждаше типове и нямаше никаква гаранция при компилация. С добавянето на generics и autoboxing/unboxing Collections Framework се превърна в типобезопасна, изразителна алтернатива на STL, идваща от кутията. Тази глава разглежда основните интерфейси, начина им на ползване и механизма за обхождане.

Йерархия на Java Collections Framework Йерархия на интерфейсите и основните имплементации в Collections Framework. Collection и Map са двете независими йерархии.

1. Еволюция и мотивация

1.1 Ранни ограничения (преди Java 5.0)

Двата оригинални класа контейнери — Vector и Hashtable — имат два съществени недостатъка:

  1. Анонимни типове — работят с Object; при извличане е задължително ръчно привеждане и се губи типова безопасност;
  2. Без поддръжка на прости типове — задължително обгръщане в wrapper класове с произтичащ режиен разход.

1.2 Collections Framework

Рамка, добавена за разширяване на оригиналните класове:

  • Нови интерфейсни абстракции;
  • Множество нови имплементации;
  • Алгоритми (в java.util.Collections);
  • Реализация на STL концепции в Java.

Пакет: java.util.

1.3 Две основни йерархии

  1. Collection — контейнер, съхраняващ обекти (елементи);
  2. Map — двойки ключ/стойност; не наследява Collection.

1.4 Начин на реализация

Основата е upcasting към интерфейс:

  • При запис типът се преобразува нагоре по йерархията;
  • При извличане без generics е нужно downcasting;
  • С generics компилаторът гарантира коректността — грешките са при компилация, не при изпълнение.

2. Интерфейс Collection

Дефинира абстракция за съхранение — не определя начин на организация (дали е наредена, дали допуска дублиране и т.н.).

2.1 Основни методи

boolean add(E element) // добавя; false при дублиране (Set); UnsupportedOperationException за readonly
boolean remove(Object element) // премахва
boolean contains(Object element) // true ако се съдържа
int size() // брой елементи
boolean isEmpty() // true ако е празна
Iterator<E> iterator() // обект за обхождане
// Групови операции:
boolean addAll(Collection<? extends E> c)
boolean removeAll(Collection<?> c)
boolean containsAll(Collection<?> c)

3. Параметризирани колекции (Generics)

// С generics — типова безопасност:
Collection<Date> dates = new ArrayList<Date>();
dates.add(new Date()); // OK
dates.add("foo"); // компилационна грешка
// Без generics — unchecked warning:
Collection dates = new ArrayList();
dates.add(new Date()); // позволено, но опасно

ArrayList<String> и ArrayList<Date> са различни типове — присвояването между тях е компилационна грешка.

4. Преобразуване между масиви и колекции

Колекция → масив:

List<String> list = ...;
String[] arr = list.toArray(new String[0]); // предпочитаният вариант
Object[] raw = list.toArray(); // без type safety

Масив → колекция:

String[] myStrings = {"a", "b", "c"};
List<String> list = Arrays.asList(myStrings); // фиксиран размер — add/remove хвърля UnsupportedOperationException

Arrays съдържа и помощни методи за сортиране, търсене и сравняване на масиви.

5. Итератор (Iterator)

Iterator<E> е стандартизираният механизъм за обхождане, разделящ логиката на достъп от структурата на колекцията:

interface Iterator<E> {
boolean hasNext(); // true ако има следващ елемент
E next(); // следващ елемент (NoSuchElementException ако няма)
void remove(); // премахва последния върнат от next() елемент
}

Типично ползване:

public void printAll(Collection<String> c) {
Iterator<String> it = c.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
}

Изключения при remove():

  • UnsupportedOperationException — при колекция само за четене;
  • IllegalStateException — при remove() без предшестващ next() или два пъти подред.

5.1 for-each за колекции (Java 5.0+)

Синтактично удобство над Iterator — компилаторът го разгъва до iterator() + while:

Collection<Date> collection = ...;
for (Date date : collection) {
System.out.println(date);
}

Налично за всеки тип, имплементиращ Iterable<E>. Не е приложимо директно за Map (двусмислено — ключ или стойност).

6. Типове колекции

6.1 Интерфейс Set

  • Не допуска дублиране на елементите;
  • Проверката е базирана на equals()hashCode() за хеширани имплементации);
  • add() с вече съществуващ елемент връща false без хвърляне на изключение.

6.2 Интерфейс SortedSet

Разширява Set с управление на подредба — елементите са наредени по compareTo() или зададен Comparator:

SortedSet<E> subSet(E from, E to) // подмножество [from, to)
SortedSet<E> headSet(E to) // елементи < to
SortedSet<E> tailSet(E from) // елементи >= from
E first() // минимален елемент
E last() // максимален елемент
Comparator<? super E> comparator() // null ако естествена подредба

6.3 Интерфейс List

Линейна наредена колекция с позиционен достъп:

void add(E element) // добавя в края
void add(int index, E element) // вмъква на позиция; изместване надясно
void remove(int index) // премахва на позиция
E get(int index) // получаване; IndexOutOfBoundsException при невалиден индекс
Object set(int index, E element) // замяна; връща стария елемент

6.4 Интерфейс Map

Съхранява уникални ключ/стойност двойки. Не наследява Collection. Основна йерархия: MapSortedMap.

V put(K key, V value) // добавя/замества; връща старата стойност
V get(Object key) // стойност по ключ
boolean containsKey(Object key)
Set<K> keySet() // множеството на ключовете
Collection<V> values() // колекция от стойностите
Set<Map.Entry<K,V>> entrySet() // двойките за обхождане

Резюме

  • Collections Framework предоставя унифицирана рамка от интерфейси и класове, еквивалентни на STL в Java.
  • Две основни йерархии: Collection (елементи) и Map (ключ/стойност двойки) — Map не е Collection.
  • С Generics (Java 5.0+) колекциите получават типова безопасност при компилация; autoboxing позволява прозрачна работа с прости типове.
  • Iterator е стандартизираният механизъм за обхождане; for-each е синтактично удобство над него.
  • Set забранява дублирания; List поддържа ред и позиционен достъп; SortedSet добавя управление на подредбата.