diff --git a/docs/tasks/StreamCollection.md b/docs/tasks/StreamCollection.md new file mode 100644 index 0000000..e447a58 --- /dev/null +++ b/docs/tasks/StreamCollection.md @@ -0,0 +1,327 @@ +# Техническое задание: заполнение `MyList` посредством Stream API + +## Задача + +Реализовать механизм заполнения собственной коллекции `MyList` посредством Java Stream API без использования стандартных коллекций Java в качестве промежуточного или итогового хранилища. + +## Требования + +1. Итоговым результатом работы потока должна быть: + +```java +MyList +``` + +1. Для сбора элементов потока непосредственно в `MyList` реализовать собственный `Collector`. + +2. Стандартные коллекции Java (`List`, `ArrayList`, `LinkedList` и другие реализации `List`) запрещено использовать: + + - как итоговую коллекцию; + - как промежуточное хранилище элементов перед их переносом в `MyList`. + +3. `Student` должен создаваться только через уже существующий независимый класс `StudentBuilder`, если объект создаётся первоначально. + +4. `MyListCollector` не должен создавать или изменять объекты `Student`. Он работает с уже созданными объектами. + +5. При использовании `map` для получения нового `Student` использовать методы `with...()`, а не `StudentBuilder`. + +6. Логика заполнения `MyList` должна быть отделена от логики класса `Student`. + +7. Для добавления объектов использовать существующий метод: + +```java +add(Student student) +``` + +1. Необходимо реализовать поддержку объединения частичных результатов, необходимую для работы `Collector`. + +--- + +## `MyListCollector` + +Создать универсальный класс: + +```java +public class MyListCollector + implements Collector, MyList> { +} +``` + +Реализовать методы: + +```text +supplier() +accumulator() +combiner() +finisher() +characteristics() +``` + +Назначение: + +- `supplier()` — создать пустой `MyList`; +- `accumulator()` — добавить очередной элемент в `MyList`; +- `combiner()` — объединить две частично заполненные коллекции; +- `finisher()` — вернуть готовый `MyList`; +- `characteristics()` — определить характеристики сборщика. + +Коллектор должен работать с `Student` без специальной логики, связанной с его полями. + +--- + +## `MyCollectors` + +Создать вспомогательный класс: + +```java +public final class MyCollectors { + + public static Collector, MyList> toMyList() { + return new MyListCollector<>(); + } +} +``` + +Класс должен предоставлять удобный способ получения собственного сборщика. + +--- + +## Контракт использования + +Должна поддерживаться конструкция: + +```java +MyList students = Stream.of( + student1, + student2, + student3 +).collect(MyCollectors.toMyList()); +``` + +где `student1`, `student2`, `student3` — валидные объекты `Student`, предварительно созданные через `StudentBuilder`. + +Результатом должен быть именно `MyList`. + +--- + +## Пример `map` + +`map` должен использоваться для преобразования одного `Student` в новый `Student`. + +Исходный объект не изменяется. Для создания нового объекта использовать `with...()`. + +Одно из преобразуемых полей должно быть `String` и формироваться по заданному бизнес-правилу: + +```text +Заглавная латинская буква + двузначное число +``` + +Например: + +```text +A07 +B12 +Z35 +``` + +Пример: + +```java +MyList students = Stream.of( + student1, + student2, + student3 +) +.map(student -> student.withGroupNumber( + generateGroupNumber(student) +)) +.collect(MyCollectors.toMyList()); +``` + +Пример функции формирования значения: + +```java +private static String generateGroupNumber(Student student) { + int number = Math.abs(student.getRecordBookNumber().hashCode() % 100); + char letter = (char) ('A' + + Math.abs(student.getRecordBookNumber().hashCode()) % 26); + + return String.format("%c%02d", letter, number); +} +``` + +Результат `map` должен оставаться `Stream`, после чего он непосредственно собирается в `MyList`. + +Формат создаваемого `groupNumber` должен соответствовать: + +```text +^[A-Z][0-9]{2}$ +``` + +--- + +## Пример `filter` совместно с `map` + +Проверить совместную работу промежуточных операций: + +```java +MyList students = Stream.of( + student1, + student2, + student3 +) +.filter(student -> student.getAverageGrade() >= 4.0) +.map(student -> student.withGroupNumber( + generateGroupNumber(student) +)) +.collect(MyCollectors.toMyList()); +``` + +Проверить, что: + +- в результат попали только студенты со средним баллом не ниже `4.0`; +- каждый результат является новым `Student`; +- `groupNumber` соответствует формату `[A-Z][0-9]{2}`; +- исходные объекты не изменились. + +--- + +## Параллельные потоки + +`combiner()` должен корректно объединять две частично сформированные `MyList`. + +Реалистичный пример: + +```java +MyList students = IntStream.range(0, 10_000) + .parallel() + .mapToObj(i -> student1.withGroupNumber( + String.format("%c%02d", + 'A' + (i % 26), + i % 100) + )) + .collect(MyCollectors.toMyList()); +``` + +Проверить: + +- результат содержит `10_000` объектов; +- ни один объект не потерян; +- количество объектов не превышает ожидаемого; +- каждый `groupNumber` соответствует формату `[A-Z][0-9]{2}`; +- объекты `Student` не изменяются после создания; +- `combiner()` корректно объединяет частичные результаты. + +--- + +## Примеры тестов + +### Заполнение несколькими `Student` + +Создать три валидных объекта `Student` через `StudentBuilder` и собрать их: + +```java +MyList result = Stream.of( + student1, + student2, + student3 +).collect(MyCollectors.toMyList()); +``` + +Проверить: + +```text +result != null +result.size() == 3 +``` + +и соответствие элементов исходному потоку. + +### Проверка порядка + +Для: + +```text +student1 +student2 +student3 +``` + +проверить: + +```text +result.get(0) == student1 +result.get(1) == student2 +result.get(2) == student3 +``` + +### Пустой поток + +```java +MyList result = Stream.empty() + .collect(MyCollectors.toMyList()); +``` + +Проверить: + +```text +result != null +result.isEmpty() +``` + +### Проверка `map` + +Преобразовать несколько `Student` через `withGroupNumber()`. + +Проверить: + +- исходные объекты не изменились; +- созданные объекты отличаются от исходных; +- `groupNumber` соответствует формату `[A-Z][0-9]{2}`. + +### Проверка `filter` и `map` + +Проверить, что после `filter` и `map` в `MyList` попадают только необходимые студенты с корректно сформированным `groupNumber`. + +### Параллельный поток + +Создать поток из `10_000` объектов через `parallel()`. + +Проверить размер итоговой коллекции, отсутствие потери элементов и корректность `groupNumber`. + +--- + +## Ограничения + +Запрещено: + +- использовать `Collectors.toList()`; +- использовать `ArrayList`, `LinkedList` или другие стандартные коллекции для накопления результата; +- сначала собирать `Student` в стандартную коллекцию, а затем переносить их в `MyList`; +- создавать `Student` внутри `MyListCollector`; +- изменять существующий `Student`; +- использовать `StudentBuilder` внутри `map` вместо `with...()`. + +## Результат + +Реализовать: + +```text +MyListCollector.java +MyCollectors.java +``` + +Использовать существующие: + +```text +Student.java +StudentBuilder.java +MyList.java +MyLinkedList.java +``` + +### Рекомендация по архитектуре + +`MyListCollector` должен зависеть только от интерфейса `MyList`, а не от конкретной реализации `MyLinkedList`. + +`MyCollectors` должен содержать только методы создания собственных сборщиков и не должен содержать бизнес-логику.