Колекции и интерфейс Collection
Структури от данни
Структурата от данни представлява начин за организиране и съхраняване на информацията, така че достъпът до нея и обработката ѝ да бъдат ефективни.
Най-често използваните структури от данни са:
- Масиви (Arrays)
- Свързани списъци (Linked Lists)
- Хеш таблици (Hash Tables)
- Дървета (Trees)
Независимо от конкретната структура, най-често върху нея се извършват следните операции:
- добавяне
- триене
- търсене
- обхождане
В Java структурите от данни се реализират чрез масиви и чрез Java Collections Framework (JCF). В настоящото упражнение ще бъдат разгледани интерфейсите, наследяващи Collection, а интерфейсът Map ще бъде представен отделно.
Java Collections Framework
Java Collections Framework (JCF) представлява набор от интерфейси и класове, предназначени за работа с групи от обекти.
Framework-ът предоставя готови реализации на различни структури от данни и унифициран набор от операции за работа с тях: добавяне; премахване; търсене; обхождане; сортиране.
Колекциите са:
- динамични
- гъвкави
- предоставят богата функционалност;
- работят само с обекти (референтни типове)
Масиви и колекции
| Характеристика | Масив | Колекция |
|---|---|---|
| Размер | Фиксиран | Динамичен |
| Типове данни | Примитивни и референтни | Само референтни |
| Дублиране | Позволява | Зависи от типа |
| Достъп | По индекс | Според конкретната колекция |
| Функционалност | Ограничена | Богата |
| Гъвкавост | Ниска | Висока |
Масивите са подходящи, когато размерът на данните е известен предварително и е необходим бърз достъп по индекс.
Колекциите са предпочитан избор, когато броят на елементите се променя динамично или се налага често добавяне, премахване, търсене и сортиране.
Интерфейс Collection
classDiagram
Iterable <|-- Collection
Collection <|-- List
Collection <|-- Set
Collection <|-- Queue
List <|.. ArrayList
List <|.. LinkedList
List <|.. Vector
Set <|.. HashSet
Set <|.. LinkedHashSet
Set <|.. TreeSet
Queue <|.. LinkedList
Queue <|.. PriorityQueue
Queue <|.. ArrayDeque
Основните интерфейси в тази част от йерархията предоставят различни групи операции:
| Интерфейс | Основни операции |
|---|---|
Iterable | iterator() |
Collection | size(), add(element), remove(element), contains(element), iterator() |
List | get(index), set(index, element) |
Set | съхраняване на уникални елементи |
Queue | offer(element), poll(), peek() |
Интерфейсът Collection е базовият интерфейс в Java Collections Framework. Той дефинира общите операции, които се поддържат от повечето колекции. Най-често използваните методи са:
//връща броя на елементите в колекцията
int size()
//проверява и връща дали е празна колекцията
boolean isEmpty()
//проверява и връща дали колекцията съдържа предавания като параметър елемент
boolean contains(Object element)
//добавя елемент в колекция (връща истина при успешно добавяне)
boolean add(E element)
//премахва елемент от колекция (връща истина при успешно изтриване)
boolean remove(Object element)
//извлича и връща итератора на колекцията
Iterator<E> iterator()
//проверява и връща дали всички елементи се съдържат в колекцията
boolean containsAll(Collection<?> c)
//добавя всички обекти от списъка към колекцията
boolean addAll(Collection<? extends E> c)
//премахва всички обекти от списъка към колекцията
boolean removeAll(Collection<?> c)
//премахва всички елементи от колекцията, които не присъстват в подавания списък от елементи
boolean retainAll(Collection<?> c)
//премахва всички елементи от колекцията
void clear()
//преобразува колекцията в масив от класа Ojbect
Object[] toArray()
//преобразува колекцията в масив от програмно дефиниран клас
<T> T[] toArray(T[] a)
Пример:
public class Application {
public static void main(String[] args) {
Collection<String> fruits = new ArrayList<>();
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Orange");
System.out.println(fruits.contains("Banana"));
System.out.println(fruits.size());
fruits.remove("Banana");
System.out.println(fruits);
}
}
В примера към колекцията се добавят три елемента. След това се проверява дали тя съдържа определена стойност, премахва се един елемент и се извежда крайното съдържание:
true
3
[Apple, Orange]
Интерфейс List
List представлява подредена колекция, която:
- позволява дублиране на елементи;
- запазва реда на добавяне;
- поддържа достъп по индекс;
- позволява произволен достъп до елементите.
Подходящ е за:
- списъци;
- последователности;
- работни опашки;
- колекции, в които редът има значение.
Най-често използвани реализации::
- ArrayList – бърз достъп по индекс, по-бавно вмъкване и изтриване в средата;
- LinkedList – по-бавно търсене, но бързо добавяне и премахване;
- Vector – синхронизирана, по-стара реализация; в съвременните приложения се използва сравнително рядко.
Пример:
List<String> names = new ArrayList<>();
names.add("Ivan");
names.add("Maria");
names.add("Georgi");
System.out.println(names.get(1));
names.remove(0);
System.out.println(names);
Методът get() извлича елемент по индекс, а remove() премахва елемент от списъка:
Maria
[Maria, Georgi]
Интерфейс Set
Set представлява колекция, която:
- не позволява дублиране на елементи;
- не поддържа индекси;
- съхранява само уникални елементи.
Подходящ е при:
- филтриране на дублирани стойности;
- работа с множества;
- бързо търсене на уникални елементи.
Най-често използвани реализации:
- HashSet – най-бърза реализация, без гарантиран ред;
- LinkedHashSet – запазва реда на добавяне;
- TreeSet – поддържа автоматично сортирани елементи.
Пример:
Set<String> cities = new HashSet<>();
cities.add("Varna");
cities.add("Sofia");
cities.add("Varna");
System.out.println(cities);
Резултат:
[Varna, Sofia]
Интерфейс Queue
Queue (опашка) реализира принципа FIFO (First In – First Out).
Новите елементи се добавят в края на опашката, а обработката започва от първия добавен елемент.
Използва се при:
- обработка на заявки;
- буфери;
- producer-consumer задачи;
- алгоритми като BFS.
Основните методи са:
| Добавяне | Премахване | Преглед |
|---|---|---|
| add() | remove() | element() |
| offer() | poll() | peek() |
Използването на add(), remove() и element() е подходящо, когато липсата на възможност за изпълнение се счита за програмна грешка и трябва незабавно да бъде сигнализирана чрез изключение. Ако подобна ситуация се очаква и трябва да бъде обработена по нормален начин, по-подходящи са offer(), poll() и peek().
Най-често използвани реализации:
- LinkedList
- PriorityQueue
- ArrayDeque
Queue<String> queue = new LinkedList<>();
queue.offer("Task 1");
queue.offer("Task 2");
queue.offer("Task 3");
System.out.println(queue.peek());
System.out.println(queue.poll());
System.out.println(queue.peek());
Методът peek() връща първия елемент, без да го премахва, а poll() го извлича и премахва от опашката:
Task 1
Task 1
Task 2
Итератори
Всички колекции, наследяващи Collection, имплементират интерфейса Iterable, което позволява използването на foreach. За по-гъвкаво обхождане се използва интерфейсът Iterator.
Итераторът е интерфейс, който предоставя стандартен механизъм за последователно обхождане на елементите в дадена колекция, без да е необходимо да се познава вътрешната й реализация. Концептуално той може да се разглежда като курсор, който последователно преминава през елементите на колекцията.
public interface Iterable<T> {
Iterator<T> iterator();
}
public interface Iterator<E> {
boolean hasNext();
E next();
void remove();
}
Основните методи са:
- hasNext() – проверява дали има следващ елемент;
- next() – връща следващия елемент;
- remove() – премахва последния върнат елемент.
Пример:
Iterator<String> iterator = names.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
Сортиране на колекции
Сортирането на елементите в колекции от тип List може да се реализира чрез интерфейсите Comparable и Comparator.
Comparable
Интерфейсът Comparable определя естествения ред на обектите.
Пример:
public class Book implements Comparable<Book> {
private int publishingYear;
@Override
public int compareTo(Book other) {
return Integer.compare(publishingYear, other.publishingYear);
}
}
Използване:
Collections.sort(books);
Comparator
Интерфейсът Comparator позволява дефиниране на външни критерии за сортиране.
public class AuthorComparator implements Comparator<Book> {
@Override
public int compare(Book b1, Book b2) {
return b1.getAuthor().compareTo(b2.getAuthor());
}
}
Използване:
Collections.sort(books, new AuthorComparator());
Анонимен Comparator
Анонимен клас може да се използва, когато критерият за сортиране е необходим само на едно място. В този случай не се създава отделен именуван клас.
books.sort(new Comparator<Book>() {
@Override
public int compare(Book b1, Book b2) {
return b1.getTitle().compareTo(b2.getTitle());
}
});
Lambda израз
Lambda изразът е кратък синтаксис за реализиране на функционален интерфейс. Функционален интерфейс е интерфейс с един абстрактен метод. Comparator има един основен абстрактен метод - compare, затова може да бъде записан чрез lambda израз.
books.sort((b1, b2) -> Double.compare(b1.getPrice(), b2.getPrice()));
Параметрите b1 и b2 са двата обекта, които се сравняват. Изразът след -> връща резултат от сравнението.
Method Reference
Method reference е още по-кратък запис, когато lambda изразът само извиква съществуващ метод. При сортиране може да се използва с помощни методи от Comparator.
books.sort(Comparator.comparingInt(Book::getPublishingYear));
Записът Book::getPublishingYear означава, че за всеки обект от тип Book се извиква методът getPublishingYear(). Получената стойност се използва като критерий за сортиране.
Анонимен клас, lambda израз и method reference могат да опишат един и същ критерий за сортиране. Разликата е в синтаксиса и краткостта на записа.
Comparable vs Comparator
| Comparable | Comparator |
|---|---|
| Определя естествения ред | Определя външен критерий |
| Реализира се в класа | Реализира се в отделен клас или чрез lambda |
| Един критерий | Възможни са множество критерии |
| Метод compareTo() | Метод compare() |