Практическое введение в JCF (Java Collections Framework) на примерах
от Kalter
В Java предусмотрено несколько способов хранения ссылок на объекты. Встроенным типом является массив, однако, ограниченное количество объектов может не всегда подойти для вашей программы. Библиотека java.util.* содержит достаточно полный набор классов контейнеров, обладающих весьма изощрёнными возможностями. Цель данной статьи - дать основные концепции и научить применять ту, или иную реализацию при решении определённых задач.
Основные интерфейсы и их реализации
В библиотеке коллекций Java существует два базовых интерфейса:
# Collection - определены основные методы для работы с данными
# Map - описывает коллекцию, состоящую из пар (ключ-значение)
Интерфейс Collection реализуют интерфейсы List, Set и Queue:
# List - пронумерованная, неупорядоченная, допускающая повторяющиеся элементы коллекция
# Set - не упорядоченная, не допускающая повторяющихся элементов, коллекция
# Queue - очередь, принцип FIFO
Реализации интерфейса List:
# ArrayList - инкапсуляция массива
# LinkedList - двусвязный список
Так как оба класса имеют совершенно разные реализации, отсюда вытекают различия, влияющие на производительность, при использовании того или иного списка:
Реализации интерфейса Set:
# HashSet - инкапсулирует HashMap, то есть для хранения хэш-таблицу. Выигрыш из этого состоит в том, что оно обеспечивает константное время выполнения следующих методов: add, contains, remove и size (при этом не исключая большие наборы)
# LinkedHashSet - связанный список элементов набора в порядке добавления, что позволяет организовывать упорядоченную итерацию вставки в набор
# TreeSet - элементы упорядочены по значениям дерева. Отсюда следует, что операции add, remove и contains можно вычислить, используя формулу: time=log(number)
Реализации интерфейса Queue:
# PriorityQueue - прямая реализация, которая упорядочивает элементы по их натуральному порядку или по собственноручно написанной реализацией Comparator, который передаём в конструктор
Реализации интерфейса Map:
# HashMap - основан на хэш-таблицах
# LinkedHashMap - расширяя класс HashMap, создаёт связанный список элементов в карте, расположенных в порядке добавления
# TreeMap - объекты сохраняются в отсортированном порядке по возрастанию. Блестящий выбор для хранения больших объёмов отсортированной информации, что делает поиск значительно быстрым
# WeakHashMap - использует слабые ссылки, отличающиеся тем, что не учитывается сборщик мусора при выявлении объектов, подлежащих удалению
Стоит сказать так же и об устаревших коллекциях:
# Enumerator - аналог интерфейса Iterator
# Vector - аналог класса ArrayList
# HashTable - аналог класса HashMap
# Stack - производный от класса Vector (принцип LIFO)
# Dictionary - аналог Map (хоть и является абстрактным классом)
Их использование не является запрещённым, но всё же не желательно
Пример 1
Создадим склад огурцов, в который будем добавлять огурцы. Больше ничего, кроме как взятие и добавление мы использовать не будем, следовательно, для решения идеально подойдёт список, а именно ArrayList.
Обратите внимание, наш склад реализует интерфейс Iterable - это нужно для поддержки синтаксиса foreach.
С LinkedList код почти не изменится. Домашнее задание: заполните склад миллионом огурцов, уберите вывод и протестируйте скорость выполнения, если на складе используется контейнер:
1. ArrayList
2. LinkedList
Выяснить, кто сделал работу быстрее и почему.
Пример 2
Огурцовый склад растёт... Со временем мы понимаем, что допустили ошибку при использовании списка для хранения наших огурцов. Пишем новый склад, основанный на множестве.
Домашнее задание: убрать поиск и вывод, заполнить склад миллионом огурцов и протестировать скорость выполнения, если на складе использовать контейнер:
1. HashSet
2. LinkedHashSet
3. TreeSet
Выяснить, кто сделал работу быстрее и почему.
Пример 3
Выделим под каждый огурчик собственный класс и добавим их в очередь. Потом эту очередь можно передать куда-нибудь, например, на раздачу банов... Всё честно, всё по правилам.
Домашнее задание: сделать так, чтобы раздача банов происходила от старших огурцов к младшим. То есть сначала раздаём баны, тем, у кого наивысший возраст, а потом младше и младше... И так до конца.
Пример 4
У каждого огурчика есть свой код, специфичный для него и весьма сложный, понятный только ему одному (или иначе "пароль"). Напишем программу, которая для каждого огурчика будет возвращать его пароль.
Домашнее задание: объяснить, почему в качестве контейнера выбрали именно TreeSet, а не любой другой, реализующий Map. Предполагается, что огурцов очень много.
Надеюсь, эти примеры, и задания к ним вам окажутся очень полезными. Однако, встаёт такой вопрос: где же хранить огурцы? А для чего они вам нужны, там и хранить. Я думаю, для вас, после четырёх примеров выше, не составит труда определить, что и где хранить.
Нижеследующий список литературы прилагается тем, кто хочет расширить свои знания о JCF, а так же он поможет вам для понимания, приведённых мной, примеров и решения домашнего задания к каждому из них:
Глава о Контейнерах в Thinking in Java
Статья-справочник на Habrahabr
Ещё одна хорошая статья
Самые задаваемые вопросы о коллекциях и ответы на них
Контейнеры Java - необходимый инструмент. Благодаря им код, написанный вами, станет более простым, эффективным и мощным. Ниже можно скачать исходные коды всех примеров в одном архиве.
JCF.zip
Основные интерфейсы и их реализации
В библиотеке коллекций Java существует два базовых интерфейса:
# Collection - определены основные методы для работы с данными
# Map - описывает коллекцию, состоящую из пар (ключ-значение)
Интерфейс Collection реализуют интерфейсы List, Set и Queue:
# List - пронумерованная, неупорядоченная, допускающая повторяющиеся элементы коллекция
# Set - не упорядоченная, не допускающая повторяющихся элементов, коллекция
# Queue - очередь, принцип FIFO
Реализации интерфейса List:
# ArrayList - инкапсуляция массива
# LinkedList - двусвязный список
Так как оба класса имеют совершенно разные реализации, отсюда вытекают различия, влияющие на производительность, при использовании того или иного списка:
Реализации интерфейса Set:
# HashSet - инкапсулирует HashMap, то есть для хранения хэш-таблицу. Выигрыш из этого состоит в том, что оно обеспечивает константное время выполнения следующих методов: add, contains, remove и size (при этом не исключая большие наборы)
# LinkedHashSet - связанный список элементов набора в порядке добавления, что позволяет организовывать упорядоченную итерацию вставки в набор
# TreeSet - элементы упорядочены по значениям дерева. Отсюда следует, что операции add, remove и contains можно вычислить, используя формулу: time=log(number)
Реализации интерфейса Queue:
# PriorityQueue - прямая реализация, которая упорядочивает элементы по их натуральному порядку или по собственноручно написанной реализацией Comparator, который передаём в конструктор
Реализации интерфейса Map:
# HashMap - основан на хэш-таблицах
# LinkedHashMap - расширяя класс HashMap, создаёт связанный список элементов в карте, расположенных в порядке добавления
# TreeMap - объекты сохраняются в отсортированном порядке по возрастанию. Блестящий выбор для хранения больших объёмов отсортированной информации, что делает поиск значительно быстрым
# WeakHashMap - использует слабые ссылки, отличающиеся тем, что не учитывается сборщик мусора при выявлении объектов, подлежащих удалению
Стоит сказать так же и об устаревших коллекциях:
# Enumerator - аналог интерфейса Iterator
# Vector - аналог класса ArrayList
# HashTable - аналог класса HashMap
# Stack - производный от класса Vector (принцип LIFO)
# Dictionary - аналог Map (хоть и является абстрактным классом)
Их использование не является запрещённым, но всё же не желательно
Пример 1
Создадим склад огурцов, в который будем добавлять огурцы. Больше ничего, кроме как взятие и добавление мы использовать не будем, следовательно, для решения идеально подойдёт список, а именно ArrayList.
- import java.util.List;
- import java.util.ArrayList;
- import java.util.Iterator;
- /**
- * First example
- * Warehouse Cucumbers
- * @author Kalter
- */
- public class WarehouseCucumbersV1<E> implements Iterable<E> {
- public static void main(String[] args) {
- WarehouseCucumbersV1<String> wc = new WarehouseCucumbersV1<String>(){{
- //добавляем элементы на склад
- add("aNNiMON");
- add("HoldFast");
- add("Kalter");
- add("Ksakep");
- add("SeTSeR");
- add("web_demon");
- //etc...
- }};
- //выводим на stdout
- for (String s: wc) {
- System.out.println(s);
- }
- }
- //наш контейнер
- protected final List<E> warehouse = new ArrayList<>();
- public void add(E e) {
- warehouse.add(e);
- }
- public E get(int index) {
- return warehouse.get(index);
- }
- public int size() {
- return warehouse.size();
- }
- @Override
- public Iterator<E> iterator() {
- return new Iterator<E>() {
- private int index = 0;
- @Override
- public boolean hasNext() {
- return index < warehouse.size();
- }
- @Override
- public E next() {
- return warehouse.get(index++);
- }
- @Override
- public void remove() {
- throw new UnsupportedOperationException("Не реализован");
- }
- };
- }
- }
Обратите внимание, наш склад реализует интерфейс Iterable - это нужно для поддержки синтаксиса foreach.
С LinkedList код почти не изменится. Домашнее задание: заполните склад миллионом огурцов, уберите вывод и протестируйте скорость выполнения, если на складе используется контейнер:
1. ArrayList
2. LinkedList
Выяснить, кто сделал работу быстрее и почему.
Пример 2
Огурцовый склад растёт... Со временем мы понимаем, что допустили ошибку при использовании списка для хранения наших огурцов. Пишем новый склад, основанный на множестве.
- import java.util.Set;
- import java.util.HashSet;
- import java.util.Iterator;
- import java.util.Scanner;
- /**
- * Second example
- * Warehouse cucumbers
- * @author Kalter
- */
- public class WarehouseCucumbersV2<E> implements Iterable<E> {
- public static void main(String[] args) {
- WarehouseCucumbersV2<String> wc = new WarehouseCucumbersV2<>();
- //заполняем склад
- Scanner scan = new Scanner(System.in);
- String s;
- System.out.print("read\n>> ");
- while (!(s=scan.nextLine()).equals("-1")) {
- wc.add(s);
- System.out.print(">> ");
- }
- //вспоминаем, кто уничтожил статусы, цветные ники и монетки
- wc.die("web_demon");
- //проверяем
- System.out.println("set:");
- System.out.println(wc);
- }
- protected final Set<E> warehouse = new HashSet<>();
- public void add(E e) {
- warehouse.add(e);
- }
- public boolean contains(E e) {
- return warehouse.contains(e);
- }
- public void die(E e) {
- Iterator<E> i = iterator();
- while (i.hasNext()) {
- E cucumber = i.next();
- if (cucumber.equals(e)) {
- i.remove();
- break;
- }
- }
- }
- @Override
- public Iterator<E> iterator() {
- return warehouse.iterator();
- }
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
- Iterator<E> i = iterator();
- while (i.hasNext()) {
- sb.append(">> " + i.next().toString() + '\n');
- }
- return sb.toString();
- }
- }
Домашнее задание: убрать поиск и вывод, заполнить склад миллионом огурцов и протестировать скорость выполнения, если на складе использовать контейнер:
1. HashSet
2. LinkedHashSet
3. TreeSet
Выяснить, кто сделал работу быстрее и почему.
Пример 3
Выделим под каждый огурчик собственный класс и добавим их в очередь. Потом эту очередь можно передать куда-нибудь, например, на раздачу банов... Всё честно, всё по правилам.
- import java.util.Queue;
- import java.util.PriorityQueue;
- import java.util.Scanner;
- /**
- * Third example
- * Cucumbers...
- * @author Kalter
- */
- public class Example3 {
- public static void main(String[] args) {
- Scanner scan = new Scanner(System.in);
- Queue<Cucumber> cucumber = new PriorityQueue<>();
- //заполняем очередь
- System.out.println("next (1/-1)?");
- String s;
- while (!(s = scan.nextLine()).equals("-1")) {
- System.out.print("name: ");
- String name = scan.nextLine();
- System.out.print("age: ");
- int age = Integer.parseInt(scan.nextLine());
- cucumber.offer(new Cucumber(name,age));
- System.out.println("next (1/-1)?");
- }
- ban(cucumber);
- }
- //раздача банов
- private static void ban(Queue<Cucumber> q) {
- Cucumber c;
- while ((c = q.poll())!=null) {
- System.out.printf("%s -> ban\n",c.getName());
- }
- }
- }
- class Cucumber implements Comparable<Cucumber> {
- private String name;
- private int age;
- public Cucumber(String name, int age) {
- this.name = name;
- this.age = age;
- }
- public String getName() {
- return name;
- }
- public int getAge() {
- return age;
- }
- public void bithday() {
- age++;
- }
- @Override
- public int compareTo(Cucumber c) {
- return 0;
- }
- }
Домашнее задание: сделать так, чтобы раздача банов происходила от старших огурцов к младшим. То есть сначала раздаём баны, тем, у кого наивысший возраст, а потом младше и младше... И так до конца.
Пример 4
У каждого огурчика есть свой код, специфичный для него и весьма сложный, понятный только ему одному (или иначе "пароль"). Напишем программу, которая для каждого огурчика будет возвращать его пароль.
- import java.util.Map;
- import java.util.TreeMap;
- import java.util.Scanner;
- /**
- * Fourth example
- * Logins and passwords
- * @author Kalter
- */
- public class Password {
- public static void main(String[] args) {
- Scanner scan = new Scanner(System.in);
- Map<String,String> map = new TreeMap<>();
- System.out.print("count: ");
- int count = Integer.parseInt(scan.nextLine());
- //заполняем нашу карту
- while (count --> 0) {
- System.out.print("login: ");
- String login = scan.nextLine();
- System.out.print("passwords: ");
- String password = scan.nextLine();
- map.put(login,password);
- }
- //выводим пароль, соответствующий логину из карты
- String choise;
- System.out.printf("finish\n\nlogin: ");
- while(!(choise=scan.nextLine()).equals("-1")){
- System.out.println("password: " + map.get(choise));
- System.out.printf("\nlogin: ");
- }
- }
- }
Домашнее задание: объяснить, почему в качестве контейнера выбрали именно TreeSet, а не любой другой, реализующий Map. Предполагается, что огурцов очень много.
Надеюсь, эти примеры, и задания к ним вам окажутся очень полезными. Однако, встаёт такой вопрос: где же хранить огурцы? А для чего они вам нужны, там и хранить. Я думаю, для вас, после четырёх примеров выше, не составит труда определить, что и где хранить.
Нижеследующий список литературы прилагается тем, кто хочет расширить свои знания о JCF, а так же он поможет вам для понимания, приведённых мной, примеров и решения домашнего задания к каждому из них:
Глава о Контейнерах в Thinking in Java
Статья-справочник на Habrahabr
Ещё одна хорошая статья
Самые задаваемые вопросы о коллекциях и ответы на них
Контейнеры Java - необходимый инструмент. Благодаря им код, написанный вами, станет более простым, эффективным и мощным. Ниже можно скачать исходные коды всех примеров в одном архиве.
JCF.zip
- /*author: Kalter*/