# Техническое задание: заполнение `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` должен содержать только методы создания собственных сборщиков и не должен содержать бизнес-логику.