Лабораторная работа № 5 — «Collections Framework и Generics: репозиторий финансовых операций»
На эту тему РПД выделяет больше всего практического времени среди базовых тем — шесть часов — и это оправданно: здесь впервые соединяются структуры данных, алгоритмическая сложность, equals/hashCode, сортировка и параметризация типов.
Работу целесообразно разделить на две части.
Первая часть — выбор коллекции по семантике.
Приложение должно хранить:
List<Transaction> — последовательность операций; Set<Category> — уникальные категории; Map<UUID, Transaction> — поиск транзакции по id; TreeMap<LocalDate, ...> — данные в порядке дат.
List моделирует упорядоченную последовательность и допускает повторяющиеся элементы; Map связывает уникальные ключи со значениями. HashSet не гарантирует порядок итерации, зато его базовые операции при нормальном распределении хешей имеют ожидаемую постоянную стоимость; TreeMap, напротив, поддерживает сортировку ключей и гарантирует логарифмическую стоимость containsKey, get, put и remove.
Задания:
найти транзакцию по UUID; получить все уникальные категории; сгруппировать суммы по дате без Stream API; удалить операцию; отсортировать операции; определить повторяющиеся категории;
Студент обязан для каждой задачи письменно выбрать структуру и объяснить почему, а не просто выполнить всё через ArrayList.
Эксперимент ArrayList vs LinkedList.
Реализуются четыре сценария:
последовательный проход; get(i) по случайным индексам; добавление в конец; вставка через ListIterator.
Цель не в получении «идеального бенчмарка», а в сопоставлении наблюдений с моделью структуры данных. Отдельно нужно предупредить: один замер System.nanoTime() не превращает лабораторию в профессиональный benchmark; результаты JVM зависят от JIT, прогрева и множества факторов. Итогом является прежде всего анализ сложности, а не соревнование наносекунд.
Вторая часть — Generics.
Студент преобразует конкретное хранилище:
class TransactionRepository { ... }
в обобщённый контракт:
public interface Repository<T, ID> { void save(T entity); Optional<T> findById(ID id); List<T> findAll(); boolean deleteById(ID id); }
затем создаёт:
public interface Identifiable<ID> { ID getId(); }
и:
public class InMemoryRepository< T extends Identifiable<ID>, ID > implements Repository<T, ID> { ... }
Именно здесь хорошо вводить Optional: метод поиска больше не обязан возвращать «магический null».
Потом студент пишет generic-метод копирования/добавления и сталкивается с:
? extends T ? super T
после чего формулирует PECS своими словами.
Отдельный короткий эксперимент на type erasure:
List<String> strings = new ArrayList<>(); List<Integer> integers = new ArrayList<>(); System.out.println( strings.getClass() == integers.getClass() );
и вопросы: почему параметр типа обеспечивает compile-time type safety, но нельзя написать new T() в общем случае; почему raw types вроде List list следует избегать. Спецификация Java описывает реализацию generics через erasure: параметризованный тип во многих runtime-контекстах стирается к соответствующему raw-type представлению.
Сортировка. Transaction получает либо естественный порядок через Comparable<Transaction>, либо студент аргументирует, почему естественного порядка у финансовой операции нет. Не менее трёх альтернативных порядков задаются через Comparator:
по дате; по сумме; по категории, затем по дате.
Контрольная задача по эффективности.
Даны 100 000 транзакций. Нужно ответить, какой вариант выбрать для многократного поиска по id:
List<Transaction>
с полным проходом или:
Map<UUID, Transaction>
с индексированием по ключу, а затем провести простой эксперимент.
Автопроверка: CRUD generic repository; невозможность добавить объект неправильного типа; уникальность Set; корректность сортировок; соответствие equals/hashCode; поиск отсутствующего ID возвращает Optional.empty().
Вопросы для углубления: Чем отличаются List, Set, Map и когда применять
каждую структуру?ArrayList vs LinkedList:
отличия по сложности операций и сценарии
выбора. HashMap vs TreeMap, HashSet vs TreeSet:
чем отличаются и что дают сортируемые
структуры?Зачем нужны Comparable и
Comparator?Для чего нужны generics и какие у них
ограничения (type erasure)?
