Files
aston-team-project/docs/tasks/StreamCollection.md
T

328 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Техническое задание: заполнение `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` должен содержать только методы создания собственных сборщиков и не должен содержать бизнес-логику.