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