Наборы данных Collection

В парадигме объектно-ориентированного программирования поддерживается концепция инкапсуляции данных внутри классов. Однако это не означает, что способы размещения данных играют менее важную роль. Конечно же, используемая структура данных зависит от решаемой задачи. Способ организации данных внутри класса имеет очень важное значение для реализации методов и достижения максимальной производительности.

Интерфейс Collection является фундаментальным интерфейсом для классов Java, поддерживающих наборы данных (коллекции), в котором объявлены следующие 2 основных метода :

public interface Collection<E>
{
    boolean add (E element();
	Iterator <E> iterator();
}

Помимо них, интерфейс Collection имеет еще несколько методов, которые рассмотрены ниже.

Метод add() добавляет элемент к набору и возвращает либо значение true, если набор данных изменился, либо false в противном случае. Например, если попытаться добавить в множество уже существующий объект, то запрос add() будет проигнорирован, поскольку по определению множество не может содержать дублирующие объекты.

Метод iterator() возвращает объект-итератор, реализующий интерфейс Iterator, который используется для последовательного обращения к элементам набора данных.

Интерфейс Iterator

В интерфейсе Iterator определены следующие три основных метода:

public interface Iterator<E>
{
    E next;
    boolean hasNext();
    void remove();
}

Реализация интерфейса Iterator предполагает, что с помощью вызова метода next() можно получить следующий элемент. С помощью метода hasNext() можно узнать, есть ли следующий элемент, и не достигнут ли конец коллекции. И если элементы еще имеются, то hasNext() вернет значение true. Метод hasNext() следует вызывать перед методом next(), так как при достижении конца коллекции метод next() выбрасывает исключение NoSuchElementException. И метод remove() удаляет текущий элемент, который был получен последним вызовом next().

Пример с Iterator для перебора коллекции, метод hasNext

import java.util.*;

public class CollectionApp
{
    public static void main(String[] args)
    {
        List<String> states = new ArrayList<String>();
        states.add("Германия");
        states.add("Франция");
        states.add("Италия");
        states.add("Испания");
         
        Iterator<String> iter = states.iterator();
        while(iter.hasNext()){
            System.out.println(iter.next());
        }
    }
}

Цикл for each

Начиная с JDK 5.0 можно сократить запись цикла, используя выражение "for each"

for (String element : list) {
    System.out.println(element.toString());
}

Компилятор преобразует цикл "for each" в обычный цикл с итератором. Цикл "for each" работает с любым объектом, реализующим интерфейс Iterable, в котором объявлен единственный метод

public interface Iterable <E>
{
    Iterator <E> iterator();
}

Интерфейс Collection расширяет интерфейс Iterable. Поэтому цикл "for each" можно использовать для любого набора данных из стандартной библиотеки.

Порядок следования элементов в интераторе

Порядок перебора элементов коллекции зависит от типа и набора элементов. Если используется объект ArrayList, то итератор начинает с индекса 0 и увеличивает индекс на 1 на каждом шаге. Если объект имеет тип HashSet, то порядок следования элементов коллекции может оказаться случайным.

При использовании итератора Iterator, можно быть уверенным, что он переберет все элементы, но строить предположения о порядке, в котором элементы будут извлекаться, по меньшей мере рискованно. В большинстве случаев порядок следования элементов не имеет никакого значения.

Удаление элементов итератором

Метод remove() интерфейса Iterable удаляет элемент, возвращенный в результате последнего вызова метода next(). В большистве случаев это правильно, т.к. необходимо проверить элемент перед принятием решения об его удалении. Пример удаления первого элемента набора строк с использованием итератора :

Iterator it = states.iterator();
it.next();
it.remove();

Необходимо помнить, что перед вызовом next() и удалением remove() существует строгая взаимосвязь. Нельзя вызывать метод remove(), если ему не предшествовал вызов метода next(). При попытке сделать это будет сгенерировано исключение IllegalStateException.

При необходимости удаления двух соседних элементов нельзя дважды подряд вызвать метод remove().

it.remove();
it.remove();  // Ошибка

Нужно сначала вызвать метод next(), чтобы итератор (указатель) встал на элемент, подлежащий удалению.

it.remove();
it.next();
it.remove();  // Правильно

Универсальные вспомогательные методы, contains

Интерфейсы Collection и Iterable являются универсальными, поэтому для них можно создавать универсальные методы, работающие для любых наборов данных. Пример универсального метода contains, проверяющего наличие элемента obj в наборе данных :

public static <E> boolean contains (Collection <E> c, Object obj)
{
    for (E element : c)
        if (element.equals(obj))
            return true;
    return false;
}
it.next();
it.remove();  // Правильно

Разработчики библиотеки Java добавили ряд полезных методов в интерфейс Collection, которые должны поддерживаться во всех реализующих его классах:

  • int size ()
  • boolean isEmpty ()
  • boolean contains (Object obj)
  • boolean containsAll (Collection<?> c)
  • boolean equals (Object obj)
  • boolean addAll (Collection<? extends E> from)
  • boolean remove (Object obj)
  • boolean removeAll (Collection<?> c)
  • void clear ()
  • boolean retainAll (Collection<?> c)
  • Object[] toArray ()
  • <T> T[] toArray (T[] arrayToFill)

Применение каждым классом, реализующим интерфейс Collection, такого количества стандартных методов было бы слишком обременительным. Чтобы упростить процесс их реализации, в классе AbstractCollection оставлены абстрактными только фундаментальные методы (такие, как size() и iterator()), а на их основе реализованы все остальные стандартные методы.

public abstract class AbstractCollection <E> implements Collection <E>
{
    . . .
    public abstract Iterator <E> iterator();

    public boolean contains (Object obj)
    {
        for (E element : c) // Вызов метода iterator()
           if (element.equals(obj))
              return true;
        return false;
    }
    . . .
}

Теперь конкретный класс, представляющий набор данных, может расширить класс AbstractCollection за счет реализации метода iterator(), а метод contains() уже реализован в родительском классе AbstractCollection. Однако, если дочерний класс содержит более эффективный вариант реализации метода contains(), то его можно использовать вместо варианта родительского класса.

Методы интерфейса java.util.Collection<E>

МетодОписание
Iterator<E> iterator() Возвращает итератор для обращения к элементам набора данных.
int size() Возвращает количество элементов в наборе данных.
boolean isEmpty() Возвращает значение true, если набор пустой.
boolean contains (Object obj) Возвращает true, если набор содержит объект, эквивалентный obj.
boolean containsAll (Collection<?> other) Возвращает true, если текущий набор содержит все объекты набора данных other.
boolean add (Object element) Добавляет элемент в набор. Возвращает true, если в результате вызова метода набор данных изменился.
boolean addAll (Collection<? extends E> other) Добавляет все элементы в набор. Возвращает true, если в результате вызова метода набор данных изменился.
boolean remove (Object obj) Удаляет объект obj. Возвращает true, если в результате вызова метода набор данных изменился.
boolean removeAll (Collection<?> other) Удаляет из текущего набора данных все элементы, содержащиеся в наборе other. Возвращает true, если в результате вызова метода набор данных изменился.
void clear () Удаляет из текущего набора данных все элементы.
boolean retainAll (Collection<?> other) Удаляет из набора данных элементы, не совпадающие с теми, которые содержатся в наборе other. Возвращает true, если в результате вызова метода набор данных изменился.
Object[] toArray () Возвращает массив с объектами из набора данных.

Методы итератора java.util.Iterator<E>

МетодОписание
boolean hasNext()Возвращает значение true, если в коллекции имеется следующий элемент, иначе возвращает false
Object next()Возвращает следующий элемент. Если достигнут конец набора,то генерируется исключение NoSuchElementException
void remove()Удаляет последний прочитанный элемент. Этот метод должен быть вызван сразу же после обращения к элементу. Если после чтения элемента набор данных изменился, данный метод генерирует исключение IllegalStateException

Интерфейс ListIterator

Интерфейс Iterator предоставляет ограниченный функционал. Гораздо больший набор методов предоставляет другой итератор - интерфейс ListIterator. Данный итератор используется классами, реализующими интерфейс List, то есть классами LinkedList, ArrayList и др.

Интерфейс ListIterator расширяет интерфейс Iterator и определяет ряд дополнительных методов:

МетодОписание
void add(E obj)Вставляет объект obj перед элементом, который должен быть возвращен следующим вызовом next()
boolean hasNext()Возвращает true, если в коллекции имеется следующий элемент, иначе возвращает false
boolean hasPrevious()Возвращает true, если в коллекции имеется предыдущий элемент, иначе возвращает false
E next()Возвращает следующий элемент, если такого нет, то генерируется исключение NoSuchElementException
E previous()Возвращает предыдущий элемент, если такого нет, то генерируется исключение NoSuchElementException
int nextIndex()Возвращает индекс следующего элемента. Если такого нет, то возвращается размер списка
int previousIndex()Возвращает индекс предыдущего элемента. Если такого нет, то возвращается число -1
void remove()Удаляет текущий элемент из списка. Таким образом, этот метод должен быть вызван после методов next() или previous(), иначе будет сгенерировано исключение IllegalStateException
void set(E obj)Присваивает текущему элементу, выбранному вызовом методов next() или previous(), ссылку на объект obj

Пример использования итератора ListIterator

import java.util.*;
 
public class CollectionApp
{
    public static void main(String[] args)
    {
        ArrayList<String> states = new ArrayList<String>();
        states.add("Германия");
        states.add("Франция");
        states.add("Италия");
        states.add("Испания");
         
        ListIterator<String> listIter = states.listIterator();
         
        while(listIter.hasNext()){
            System.out.println(listIter.next());
        }
        // Текущим элементом является строка Испания
        // Изменение значения этого элемента
        listIter.set("Португалия");
        // Перебор элементов в обратном порядке
        while(listIter.hasPrevious()){
            System.out.println(listIter.previous());
        }
    }
}

Иерархия наборов данных интерфейса Collection

На рисунке представлена иерархия наборов классов, реализующих интерфейс Collection:

Иерархия наборов данных

Список наборов данных

В таблице представлен список наборов данных. Все классы с именами, оканчивающимися на Map реализуют интерфейс Map, остальные интерфейс Collection.

МетодОписание
ArrayList Индексируемая последовательность, размер которой может увеличиваться и уменьшаться.
LinkedList Упорядоченная последовательность, обеспечивающая эффективное выполнение операций включения или удаления элемента в любой позиции.
HashSet Неупорядоченный набор, не допускающий дублирования элементов.
TreeSet Сортированное множество элементов.
EnumSet Набор значений нумерованногог типа.
LinkedHashSet Множество, которое помнит порядок, в котором элементы были включены в него.
PriorityQueue Набор, обеспечивающий эффективное удаление наименьшего элемента.
HashMap Карта, которая хранит связи ключ/значение.
TreeMap Карта, в которой ключи отсортированы.
EnumMap Карта, в которой ключи принадлежат нумерованному типу.
LinkedHashMap Карта, которая помнит порядок включения элементов в него.
WeakHashMap Карта, не используемые значения которой могут быть обработаны системой сборки мусора.
IdentityHashMap Карта, для сравнения ключей которой может быть использована операция ==.
Наверх
  Рейтинг@Mail.ru