docs: add stream collection task

This commit is contained in:
2026-08-25 22:08:40 +04:00
parent 304f669868
commit 6b10c04df2
+327
View File
@@ -0,0 +1,327 @@
# Техническое задание: заполнение `MyList<Student>` посредством Stream API
## Задача
Реализовать механизм заполнения собственной коллекции `MyList<Student>` посредством Java Stream API без использования стандартных коллекций Java в качестве промежуточного или итогового хранилища.
## Требования
1. Итоговым результатом работы потока должна быть:
```java
MyList<Student>
```
1. Для сбора элементов потока непосредственно в `MyList<Student>` реализовать собственный `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<T>`
Создать универсальный класс:
```java
public class MyListCollector<T>
implements Collector<T, MyList<T>, MyList<T>> {
}
```
Реализовать методы:
```text
supplier()
accumulator()
combiner()
finisher()
characteristics()
```
Назначение:
- `supplier()` — создать пустой `MyList<T>`;
- `accumulator()` — добавить очередной элемент в `MyList<T>`;
- `combiner()` — объединить две частично заполненные коллекции;
- `finisher()` — вернуть готовый `MyList<T>`;
- `characteristics()` — определить характеристики сборщика.
Коллектор должен работать с `Student` без специальной логики, связанной с его полями.
---
## `MyCollectors`
Создать вспомогательный класс:
```java
public final class MyCollectors {
public static <T> Collector<T, MyList<T>, MyList<T>> toMyList() {
return new MyListCollector<>();
}
}
```
Класс должен предоставлять удобный способ получения собственного сборщика.
---
## Контракт использования
Должна поддерживаться конструкция:
```java
MyList<Student> students = Stream.of(
student1,
student2,
student3
).collect(MyCollectors.toMyList());
```
где `student1`, `student2`, `student3` — валидные объекты `Student`, предварительно созданные через `StudentBuilder`.
Результатом должен быть именно `MyList<Student>`.
---
## Пример `map`
`map` должен использоваться для преобразования одного `Student` в новый `Student`.
Исходный объект не изменяется. Для создания нового объекта использовать `with...()`.
Одно из преобразуемых полей должно быть `String` и формироваться по заданному бизнес-правилу:
```text
Заглавная латинская буква + двузначное число
```
Например:
```text
A07
B12
Z35
```
Пример:
```java
MyList<Student> 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<Student>`, после чего он непосредственно собирается в `MyList<Student>`.
Формат создаваемого `groupNumber` должен соответствовать:
```text
^[A-Z][0-9]{2}$
```
---
## Пример `filter` совместно с `map`
Проверить совместную работу промежуточных операций:
```java
MyList<Student> 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<Student>`.
Реалистичный пример:
```java
MyList<Student> 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<Student> 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<Student> result = Stream.<Student>empty()
.collect(MyCollectors.toMyList());
```
Проверить:
```text
result != null
result.isEmpty()
```
### Проверка `map`
Преобразовать несколько `Student` через `withGroupNumber()`.
Проверить:
- исходные объекты не изменились;
- созданные объекты отличаются от исходных;
- `groupNumber` соответствует формату `[A-Z][0-9]{2}`.
### Проверка `filter` и `map`
Проверить, что после `filter` и `map` в `MyList<Student>` попадают только необходимые студенты с корректно сформированным `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<T>`, а не от конкретной реализации `MyLinkedList<T>`.
`MyCollectors` должен содержать только методы создания собственных сборщиков и не должен содержать бизнес-логику.