Merge branch 'docs/tasks'

This commit is contained in:
2026-08-30 13:08:58 +04:00
10 changed files with 2693 additions and 0 deletions
+455
View File
@@ -0,0 +1,455 @@
# Техническое задание: поиск студентов с использованием паттерна Компоновщик
## Задача
Реализовать механизм поиска студентов по диапазону значений с использованием паттерна **Компоновщик**.
Должны поддерживаться следующие критерии поиска:
- диапазон `groupNumber`;
- диапазон `averageGrade`;
- диапазон `recordBookNumber`.
Критерии должны поддерживать объединение в составной критерий.
Режим объединения критериев — **объединение (ИЛИ)** или **пересечение (И)** — задаётся в конструкторе основного класса.
---
# Названия классов и интерфейсов
## `StudentSearchCriteria`
Интерфейс отдельного критерия поиска.
### Контракт
```java
boolean matches(Student student);
```
Метод возвращает `true`, если студент соответствует критерию.
---
## `GroupNumberRangeCriteria`
Критерий поиска по диапазону `groupNumber`.
### Конструктор
```java
GroupNumberRangeCriteria(String min, String max);
```
### Метод
```java
boolean matches(Student student);
```
`groupNumber` имеет тип `String`, поэтому сравнение выполнять лексикографически.
Границы диапазона включаются.
---
## `AverageGradeRangeCriteria`
Критерий поиска по диапазону `averageGrade`.
### Конструктор
```java
AverageGradeRangeCriteria(double min, double max);
```
### Метод
```java
boolean matches(Student student);
```
Студент соответствует критерию, если:
```text
min <= averageGrade <= max
```
---
## `RecordBookNumberRangeCriteria`
Критерий поиска по диапазону `recordBookNumber`.
### Конструктор
```java
RecordBookNumberRangeCriteria(int min, int max);
```
### Метод
```java
boolean matches(Student student);
```
Студент соответствует критерию, если:
```text
min <= recordBookNumber <= max
```
---
# `CompositeStudentSearchCriteria`
Компоновщик критериев поиска.
Класс реализует:
```java
StudentSearchCriteria
```
и содержит другие реализации `StudentSearchCriteria`.
### Методы
```java
void add(StudentSearchCriteria criteria);
```
```java
void remove(StudentSearchCriteria criteria);
```
```java
boolean matches(Student student);
```
### Логика
`CompositeStudentSearchCriteria` должен поддерживать два режима:
```text
UNION — объединение условий, ИЛИ
INTERSECTION — пересечение условий, И
```
Режим должен задаваться при создании компоновщика:
```java
CompositeStudentSearchCriteria(
SearchOperation operation
);
```
где `SearchOperation` — перечисление:
```java
public enum SearchOperation {
UNION,
INTERSECTION
}
```
Для `UNION` студент подходит, если выполняется **хотя бы один** вложенный критерий.
Для `INTERSECTION` студент подходит, если выполняются **все** вложенные критерии.
---
# `StudentSearchService`
Основной класс поиска.
Он должен принимать интерфейс `StudentSearchCriteria`, а не конкретную реализацию критерия.
### Конструктор
```java
StudentSearchService(StudentSearchCriteria criteria);
```
### Метод
```java
MyList<Student> find(MyList<Student> students);
```
### Поведение
Метод `find()` должен:
1. Последовательно проверить студентов исходной коллекции.
2. Передать каждого студента в:
```java
criteria.matches(student)
```
1. Добавить подходящих студентов в новую `MyList<Student>`.
2. Вернуть новую коллекцию.
`StudentSearchService` не должен содержать логику конкретных диапазонов или объединения критериев.
### Обработка отсутствующего критерия
Если `StudentSearchService` создан без критерия:
```java
new StudentSearchService(null);
```
необходимо выбросить:
```java
IllegalArgumentException
```
Класс не должен выполнять поиск без заданного критерия.
---
# Пример использования
### Один критерий
```java
StudentSearchCriteria criteria =
new AverageGradeRangeCriteria(4.0, 5.0);
StudentSearchService service =
new StudentSearchService(criteria);
MyList<Student> result =
service.find(students);
```
### Пересечение критериев
Например, студент должен одновременно:
- находиться в группе от `A10` до `C30`;
- иметь средний балл от `4.0` до `5.0`.
```java
CompositeStudentSearchCriteria criteria =
new CompositeStudentSearchCriteria(
SearchOperation.INTERSECTION
);
criteria.add(
new GroupNumberRangeCriteria("A10", "C30")
);
criteria.add(
new AverageGradeRangeCriteria(4.0, 5.0)
);
StudentSearchService service =
new StudentSearchService(criteria);
```
### Объединение критериев
Студент должен соответствовать хотя бы одному условию:
```java
CompositeStudentSearchCriteria criteria =
new CompositeStudentSearchCriteria(
SearchOperation.UNION
);
criteria.add(
new AverageGradeRangeCriteria(4.5, 5.0)
);
criteria.add(
new RecordBookNumberRangeCriteria(10000, 20000)
);
StudentSearchService service =
new StudentSearchService(criteria);
```
---
# Общие требования
1. `StudentSearchService` должен работать только через `StudentSearchCriteria`.
2. `StudentSearchService` не должен знать о конкретных критериях.
3. `CompositeStudentSearchCriteria` должен реализовывать тот же интерфейс `StudentSearchCriteria`.
4. Должна поддерживаться вложенность компоновщиков.
Например:
```text
Composite
├── GroupNumberRangeCriteria
└── Composite
├── AverageGradeRangeCriteria
└── RecordBookNumberRangeCriteria
```
1. Исходную `MyList<Student>` не изменять.
2. Результат возвращать в виде новой `MyList<Student>`.
3. Порядок найденных студентов должен сохраняться.
4. Стандартные `List`, `ArrayList`, `LinkedList` не использовать.
5. Готовые методы поиска не использовать.
6. При отсутствии совпадений возвращать пустую `MyList<Student>`.
7. Для диапазонов `min > max` выбрасывать `IllegalArgumentException`.
---
# Примеры тестов
## `GroupNumberRangeCriteria`
Проверить:
- значение внутри диапазона;
- значение равное нижней границе;
- значение равное верхней границе;
- значение вне диапазона.
## `AverageGradeRangeCriteria`
Проверить:
```text
3.9 → false
4.0 → true
4.5 → true
5.0 → true
5.1 → false
```
## `RecordBookNumberRangeCriteria`
Проверить значения внутри и вне диапазона, включая обе границы.
## Некорректный диапазон
Для каждого критерия проверить:
```text
min > max
```
Ожидается:
```java
IllegalArgumentException
```
## `INTERSECTION`
Создать два критерия и проверить:
```text
критерий A = true
критерий B = true
→ true
```
```text
критерий A = true
критерий B = false
→ false
```
## `UNION`
Проверить:
```text
A = true
B = false
→ true
```
```text
A = false
B = true
→ true
```
```text
A = false
B = false
→ false
```
## Компоновщик без критериев
Проверить поведение:
```java
new CompositeStudentSearchCriteria(
SearchOperation.INTERSECTION
);
```
и:
```java
new CompositeStudentSearchCriteria(
SearchOperation.UNION
);
```
Поведение должно быть явно определено и закреплено тестами.
Рекомендуется:
- пустое `INTERSECTION` → `true`;
- пустое `UNION` → `false`.
## `StudentSearchService`
Проверить:
- корректный поиск по одному критерию;
- пустой результат;
- пустую исходную коллекцию;
- сохранение порядка;
- неизменность исходной коллекции;
- работу с `CompositeStudentSearchCriteria`.
## Проверка вложенного Компоновщика
Создать компоновщик с вложенным компоновщиком и проверить корректность вычисления результата.
## Проверка отсутствующего критерия
Создать:
```java
new StudentSearchService(null);
```
Проверить `IllegalArgumentException`.
## Результат
Реализовать:
```text
StudentSearchCriteria.java
GroupNumberRangeCriteria.java
AverageGradeRangeCriteria.java
RecordBookNumberRangeCriteria.java
CompositeStudentSearchCriteria.java
SearchOperation.java
StudentSearchService.java
```
## Git
Название ветки:
```text
feature/composite-student-search
```
+324
View File
@@ -0,0 +1,324 @@
# Техническое задание: чтение и дозапись `Student` в CSV
## Задача
Реализовать два независимых класса для работы с объектами `Student` в формате CSV:
- `StudentCsvWriter` — дозапись студентов в файл;
- `StudentCsvReader` — последовательное чтение студентов из файла.
Для работы с форматом CSV использовать готовую библиотеку для работы с CSV. Самостоятельно реализовывать парсинг CSV запрещено.
Оба класса должны принимать **абсолютный путь к файлу**.
Исключения `Student` и `StudentBuilder` не обрабатывать.
---
# Формат CSV-файла
Первая строка файла должна содержать заголовок:
```text
groupNumber,averageGrade,recordBookNumber
```
Каждая следующая строка содержит данные одного студента:
```text
groupNumber,averageGrade,recordBookNumber
```
Пример:
```text
groupNumber,averageGrade,recordBookNumber
A12,4.5,10001
B07,3.8,10002
C25,4.9,10003
```
Для формирования и чтения CSV использовать стандартную библиотеку для работы с форматом CSV.
---
# `StudentCsvWriter`
## Назначение
Дозаписывать одного студента в указанный CSV-файл.
## Контракт
Конструктор:
```java
public StudentCsvWriter(String absoluteFilePath)
```
Метод:
```java
public void write(Student student) throws IOException
```
### Требования
1. Метод принимает одного `Student`.
2. Запись выполняется в режиме добавления.
3. Существующие записи не изменяются.
4. Если файл существует, заголовок повторно не записывается.
5. Если файл не существует, необходимо создать файл.
6. При создании нового файла имя должно соответствовать формату:
```text
students-yyyy-MM-dd-HH-mm.csv
```
Например:
```text
students-2026-08-28-20-45.csv
```
1. Новый файл создаётся в директории, указанной абсолютным путём.
2. При создании нового файла сначала записывается заголовок.
3. При успешной записи метод завершается без исключения.
4. Метод не возвращает значение.
5. Исключения, связанные с записью файла, не обрабатывать внутри метода — они должны передаваться вызывающему коду.
6. Исключения `Student` и `StudentBuilder` также не обрабатывать.
## Невозможность записи
Если студент не может быть записан, например:
- отсутствуют права на запись;
- невозможно создать файл;
- файл недоступен;
- произошла ошибка файловой системы;
метод должен выбросить соответствующее исключение, например `IOException`.
Запись не должна возвращать специальное значение вроде `0` для обозначения ошибки.
---
# `StudentCsvReader`
## Назначение
Последовательно читать студентов из CSV-файла.
## Контракт
Класс должен реализовывать:
```java
Iterator<Student>
```
Конструктор:
```java
public StudentCsvReader(String absoluteFilePath)
```
Методы:
```java
@Override
public boolean hasNext()
```
```java
@Override
public Student next()
```
### Требования к `hasNext()`
1. Проверяет наличие следующей записи `Student`.
2. Заголовок CSV не считается записью.
3. Повторный вызов `hasNext()` не должен пропускать следующую запись.
4. После окончания файла метод возвращает `false`.
### Требования к `next()`
1. Возвращает следующего `Student`.
2. `Student` создаётся через существующий `StudentBuilder`.
3. При отсутствии следующего элемента выбрасывается:
```text
NoSuchElementException
```
1. Весь файл не должен предварительно загружаться в коллекцию.
## Невозможность прочитать студента
Если следующую запись невозможно прочитать или преобразовать в `Student`, например:
- некорректный формат CSV;
- отсутствует обязательное поле;
- значение невозможно преобразовать в нужный тип;
- данные не проходят валидацию `Student`;
исключение должно передаваться вызывающему коду.
Исключения `Student` и `StudentBuilder` **не перехватывать и не обрабатывать**.
Некорректные записи не должны молча пропускаться.
---
# Общие требования
1. Оба класса принимают абсолютный путь к файлу.
2. Использовать готовую библиотеку для работы с CSV.
3. Не реализовывать собственный CSV-парсер.
4. Не использовать стандартные коллекции Java как основное хранилище студентов.
5. Не дублировать в этих классах валидацию `Student`.
6. Не дублировать логику создания `Student` вне `StudentBuilder`.
7. `StudentCsvWriter` отвечает только за запись.
8. `StudentCsvReader` отвечает только за чтение.
---
# Примеры тестов
## `StudentCsvWriter`
### Создание нового файла
Передать путь к несуществующему файлу и вызвать:
```java
writer.write(student);
```
Проверить:
- файл создан;
- имя соответствует `students-yyyy-MM-dd-HH-mm.csv`;
- присутствует заголовок;
- студент записан;
- метод завершился без исключения.
### Дозапись
Создать файл с одним студентом и выполнить:
```java
writer.write(student2);
```
Проверить, что:
- первая запись сохранилась;
- `student2` добавлен в конец;
- заголовок присутствует только один раз.
### Несколько последовательных записей
```java
writer.write(student1);
writer.write(student2);
writer.write(student3);
```
Проверить наличие трёх записей в правильном порядке.
### Невозможность записи
Передать путь, по которому невозможно создать или открыть файл для записи.
Проверить, что `write()` выбрасывает `IOException`.
Не проверять специальное возвращаемое значение — метод ничего не возвращает.
---
## `StudentCsvReader`
### Чтение корректного файла
Для файла:
```text
groupNumber,averageGrade,recordBookNumber
A12,4.5,10001
B07,3.8,10002
C25,4.9,10003
```
Проверить:
- `hasNext()` возвращает `true` до последнего студента;
- `next()` возвращает студентов в правильном порядке;
- после последнего студента `hasNext()` возвращает `false`.
### Повторный вызов `hasNext()`
```java
reader.hasNext();
reader.hasNext();
Student student = reader.next();
```
Проверить, что первый студент не был пропущен.
### `next()` после окончания файла
После чтения всех студентов вызвать:
```java
reader.next();
```
Проверить выброс:
```text
NoSuchElementException
```
### Некорректная запись
Передать файл:
```text
groupNumber,averageGrade,recordBookNumber
A12,4.5,10001
INVALID
C25,4.9,10003
```
Проверить, что ошибка при чтении некорректной записи передаётся вызывающему коду.
### Некорректные данные `Student`
Передать данные, нарушающие ограничения `Student`.
Проверить, что исключение от `Student` или `StudentBuilder` выходит наружу и не перехватывается `StudentCsvReader`.
---
# Названия файлов
```text
StudentCsvWriter.java
StudentCsvReader.java
```
# Git
Название ветки:
```text
feature/csv-student-io
```
Conventional Commit:
```text
feat: add csv student reader and writer
```
+44
View File
@@ -0,0 +1,44 @@
# Техническое задание: кастомный контейнер List
## Задача
Реализовать собственный контейнер для хранения объектов, не используя готовые реализации `List`, `ArrayList` и `LinkedList`.
## Требования
1. Создать интерфейс `MyList<T>`.
2. В интерфейсе определить основные операции:
- `add(...)`;
- `get(...)`;
- `set(...)`;
- `remove(...)`;
- `size()`;
- `isEmpty()`.
3. Создать класс `MyLinkedList<T>`, реализующий `MyList<T>`.
4. Реализовать контейнер на основе собственных узлов `Node<T>`.
5. Контейнер должен поддерживать:
- добавление элемента;
- получение элемента по индексу;
- замену элемента по индексу;
- удаление элемента по индексу;
- получение количества элементов;
- проверку на пустоту.
6. Обработать некорректные индексы с помощью исключений.
7. Контейнер не должен зависеть от класса `Student`, сортировки или пользовательского интерфейса.
## Результат
Должны быть реализованы:
```text
MyList.java
MyLinkedList.java
```
+368
View File
@@ -0,0 +1,368 @@
# Техническое задание: `EvenOnlySortStrategy`
## Задача
Создать класс `EvenOnlySortStrategy`, реализующий дополнительное задание по сортировке элементов с чётным значением выбранного числового поля.
Класс должен использовать **паттерн Стратегия**: он не реализует собственный алгоритм сортировки, а делегирует сортировку переданной реализации `SortStrategy`.
## Контракт
Класс:
```java
public class EvenOnlySortStrategy<T> implements SortStrategy<T>
```
Должен содержать два конструктора:
```java
public EvenOnlySortStrategy()
```
```java
public EvenOnlySortStrategy(SortStrategy<T> sortStrategy)
```
Также реализовать метод:
```java
public void setSortStrategy(SortStrategy<T> sortStrategy)
```
и основной метод интерфейса `SortStrategy`:
```java
@Override
public void sort(
MyList<T> list,
Comparator<T> comparator
)
```
Для определения чётности числового поля использовать:
```java
ToIntFunction<T>
```
Поэтому рекомендуется также передавать функцию получения числового значения через конструктор или отдельный метод настройки.
Для `Student` в качестве функции использовать:
```java
Student::getRecordBookNumber
```
## Поведение при отсутствии стратегии
Если `sortStrategy` не задан:
```java
EvenOnlySortStrategy<Student> strategy =
new EvenOnlySortStrategy<>();
```
то вызов:
```java
strategy.sort(students, comparator);
```
должен завершаться контролируемой ошибкой.
Рекомендуется выбрасывать:
```java
IllegalStateException
```
с понятным сообщением о том, что стратегия сортировки не задана.
Не следует использовать `null` как признак успешного выполнения сортировки.
## Поведение `setSortStrategy`
Метод:
```java
setSortStrategy(SortStrategy<T> sortStrategy)
```
заменяет текущую стратегию.
После вызова метода последующая сортировка должна выполняться именно переданной стратегией.
Передача `null` должна приводить к контролируемой ошибке, например:
```java
IllegalArgumentException
```
## Основная бизнес-логика
`EvenOnlySortStrategy` **не должен содержать код какого-либо алгоритма сортировки**.
Запрещено реализовывать внутри класса:
- Merge Sort;
- Bubble Sort;
- Selection Sort;
- другие алгоритмы сортировки;
- вызовы стандартных методов сортировки Java.
Класс отвечает только за:
1. определение элементов, участвующих в специальной сортировке;
2. сохранение исходных позиций нечётных элементов;
3. передачу подходящих элементов переданной `SortStrategy`;
4. восстановление результата на исходные позиции;
5. делегирование самого алгоритма сортировки.
Таким образом:
```text
EvenOnlySortStrategy
│
├── определяет, какие элементы сортировать
│
├── передаёт сортируемые элементы
│ ↓
│ SortStrategy
│ ↓
│ MergeSortStrategy
│ QuickSortStrategy
│ ...
│
└── возвращает результат на исходные позиции
```
Один `EvenOnlySortStrategy` должен работать с **любой реализацией `SortStrategy`**.
## Рекомендуемый алгоритм
Рекомендуется использовать следующий подход.
Исходная коллекция:
```text
[7, 8, 3, 2, 5, 4]
```
При выборе `recordBookNumber`:
```text
нечётные: 7, 3, 5
чётные: 8, 2, 4
```
1. Пройти исходный `MyList`.
2. С помощью `ToIntFunction<T>` определить чётность каждого элемента.
3. Запомнить позиции элементов с чётным значением.
4. Сформировать отдельный `MyList<T>` только из чётных элементов.
5. Передать эту коллекцию в `sortStrategy.sort(...)`.
6. Полученные отсортированные элементы последовательно вернуть на сохранённые позиции.
7. Элементы с нечётными значениями не изменять.
Например, переданная стратегия `MergeSortStrategy` сортирует:
```text
[8, 2, 4]
```
в:
```text
[2, 4, 8]
```
После возвращения элементов на исходные позиции:
```text
[7, 2, 3, 4, 5, 8]
```
При этом `EvenOnlySortStrategy` не знает, каким способом были отсортированы `8`, `2`, `4`.
## Работа с любой `SortStrategy`
Должно быть возможно использовать:
```java
EvenOnlySortStrategy<Student> strategy =
new EvenOnlySortStrategy<>(
new MergeSortStrategy<>(),
Student::getRecordBookNumber
);
```
А также после создания заменить стратегию:
```java
strategy.setSortStrategy(new AnotherSortStrategy<>());
```
После этого специальная сортировка должна использовать новую стратегию без изменения `EvenOnlySortStrategy`.
## Требования к `ToIntFunction`
Функция получения числового значения должна передаваться извне.
Для `Student`:
```java
Student::getRecordBookNumber
```
Стратегия не должна напрямую обращаться к:
```java
student.getRecordBookNumber()
```
или к другим полям `Student`.
Это позволяет использовать класс с другими типами объектов и другими числовыми полями.
## Требования к коллекциям
Работать необходимо с:
```java
MyList<T>
```
Использование `ArrayList`, `LinkedList` и других стандартных коллекций для хранения элементов запрещено.
Вспомогательная коллекция чётных элементов также должна быть `MyList<T>`.
## Тесты
### Переданная стратегия используется
Передать `MergeSortStrategy`.
Проверить, что сортировка чётных элементов выполняется переданной стратегией.
### Замена стратегии
1. Создать `EvenOnlySortStrategy` с одной реализацией `SortStrategy`.
2. Выполнить сортировку.
3. Вызвать `setSortStrategy(...)`.
4. Выполнить сортировку повторно.
5. Проверить, что используется новая стратегия.
### Конструктор без стратегии
Создать:
```java
new EvenOnlySortStrategy<>();
```
Вызвать `sort()`.
Проверить выброс `IllegalStateException`.
### Конструктор со стратегией
Создать объект с `MergeSortStrategy`.
Проверить успешное выполнение сортировки.
### Передача `null` в `setSortStrategy`
Проверить выброс `IllegalArgumentException`.
### Все элементы нечётные
```text
[7, 3, 5, 1]
```
Результат должен полностью совпадать с исходной коллекцией.
### Все элементы чётные
```text
[8, 2, 6, 4]
```
Ожидаемый результат:
```text
[2, 4, 6, 8]
```
### Смешанные значения
```text
[7, 8, 3, 2, 5, 4]
```
Ожидаемый результат:
```text
[7, 2, 3, 4, 5, 8]
```
### Повторяющиеся значения
Проверить корректность сортировки при наличии одинаковых чётных значений.
### Один элемент
Проверить отдельно чётный и нечётный элемент.
### Пустая коллекция
Сортировка не должна приводить к ошибке.
### Проверка сохранения нечётных элементов
Проверить не только значения, но и то, что объекты с нечётным `recordBookNumber` остались на тех же индексах.
### Проверка разных реализаций
Использовать `EvenOnlySortStrategy` с каждой доступной реализацией `SortStrategy`.
Проверить, что результат специальной сортировки соответствует контракту независимо от конкретного алгоритма.
### Проверка делегирования
Создать тестовую реализацию `SortStrategy`, которая фиксирует факт вызова `sort()`.
Передать её в `EvenOnlySortStrategy`.
Проверить, что:
- `sort()` был вызван;
- в стратегию передана коллекция только с чётными элементами;
- сама `EvenOnlySortStrategy` не выполняет сортировку самостоятельно.
## Результат
Реализовать:
```text
EvenOnlySortStrategy.java
```
Класс должен зависеть от абстракции:
```text
SortStrategy<T>
MyList<T>
Comparator<T>
ToIntFunction<T>
```
и **не должен содержать реализации конкретного алгоритма сортировки**.
## Git
Ветка:
```text
feature/even-only-sort-strategy
```
+48
View File
@@ -0,0 +1,48 @@
# Техническое задание: сортировка слиянием
## Задача
Реализовать паттерн Strategy и собственный алгоритм сортировки слиянием объектов `Student`.
## Требования
1. Создать интерфейс `SortStrategy<T>` с методом:
```text
sort(MyList<T> list, Comparator<T> comparator)
```
1. Создать класс `MergeSortStrategy<T>`, реализующий `SortStrategy<T>`.
2. Реализовать алгоритм сортировки слиянием самостоятельно.
3. Готовые методы сортировки Java не использовать.
4. Сортировка должна работать с `MyList<T>`.
5. Сортировка должна использовать переданный `Comparator<T>`.
6. Реализовать сортировку `Student` по каждому из трёх полей:
- `groupNumber`;
- `averageGrade`;
- `recordBookNumber`.
7. Создать отдельные компараторы:
- `StudentGroupComparator`;
- `StudentAverageGradeComparator`;
- `StudentRecordBookComparator`.
8. `MergeSortStrategy` не должен зависеть от класса `Student`.
## Результат
```text
SortStrategy.java
MergeSortStrategy.java
StudentGroupComparator.java
StudentAverageGradeComparator.java
StudentRecordBookComparator.java
```
+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` должен содержать только методы создания собственных сборщиков и не должен содержать бизнес-логику.
+46
View File
@@ -0,0 +1,46 @@
# Техническое задание: класс `Student`
## Задача
Реализовать класс `Student` как единственную точку проверки корректности данных. Объект класса `Student` не может быть создан с некорректными данными.
## Требования
1. Класс `Student` содержит три поля:
- `groupNumber`;
- `averageGrade`;
- `recordBookNumber`.
2. Все поля должны быть `private` и неизменяемыми после создания объекта.
3. Проверка корректности данных должна находиться в классе `Student`.
4. Реализовать класс `StudentBuilder` для создания объектов `Student`.
5. В `StudentBuilder` реализовать методы:
- `setGroupNumber(...)`;
- `setAverageGrade(...)`;
- `setRecordBookNumber(...)`;
- `build()`.
6. Метод `build()` должен создавать только корректный объект `Student`. При некорректных данных должно выбрасываться исключение.
7. Реализовать для `Student`:
- методы получения значений полей;
- `toString()`;
- `equals()`;
- `hashCode()`.
## Результат
Должны быть реализованы два независимых класса:
```text
Student.java
StudentBuilder.java
```
Классы не должны зависеть от сортировки, коллекции, меню и ввода данных.
+688
View File
@@ -0,0 +1,688 @@
# Техническое задание: многопоточный поиск индексов `Student`
## Задача
Реализовать многопоточный поиск всех вхождений заданного объекта `Student` в `MyList<Student>`.
Метод **не должен выводить никаких сообщений в консоль**.
Результатом работы должен быть массив `int[]`, содержащий индексы всех элементов исходной коллекции, которые равны переданному `Student`.
Сравнение выполнять через существующий `Student.equals()`.
---
## Входная информация
Основной метод принимает:
```java
MyList<Student> students
```
Исходная коллекция студентов.
```java
Student target
```
Студент, вхождения которого необходимо найти.
---
## Выходная информация
Метод возвращает:
```java
int[]
```
Массив должен содержать индексы всех совпавших элементов.
Пример:
```text
Исходная коллекция:
[Student A, Student B, Student A, Student C, Student A]
Результат:
[0, 2, 4]
```
Индексы должны быть расположены в порядке возрастания.
При отсутствии совпадений:
```text
[]
```
При пустой коллекции:
```text
[]
```
---
# Название класса
```text
StudentOccurrenceIndexFinder
```
---
# Публичные методы и контракты
## `findOccurrences`
```java
public int[] findOccurrences(
MyList<Student> students,
Student target
);
```
Метод должен:
1. Проверить входные параметры.
2. Разделить коллекцию на диапазоны индексов.
3. Передать каждый диапазон отдельной задаче.
4. Выполнить поиск в нескольких потоках.
5. Получить от каждого потока найденные индексы.
6. Объединить результаты.
7. Вернуть индексы в порядке возрастания.
8. Не изменять исходную коллекцию.
9. Не изменять объекты `Student`.
10. Не выводить ничего в консоль.
---
# Вспомогательные методы
Количество и вид `private` методов могут быть выбраны разработчиком.
Рекомендуется выделить:
```java
private int calculateThreadCount(int size);
```
Определяет количество рабочих потоков.
```java
private int countChunkSize(int size, int threadCount);
```
Определяет размер диапазона для одной задачи.
```java
private MyList<Integer> findInRange(
MyList<Student> students,
Student target,
int fromIndex,
int toIndex
);
```
Обрабатывает диапазон:
```text
[fromIndex, toIndex)
```
и возвращает индексы найденных совпадений.
---
# Работа с многопоточностью
## Рекомендуемый подход
Не использовать общий контейнер результатов, к которому одновременно обращаются несколько потоков.
Также **не рекомендуется использовать мьютексы** (`synchronized`, `Lock`, `ReentrantLock`) для хранения результата.
Лучше использовать модель:
```text
MyList<Student>
│
┌────────────┼────────────┐
↓ ↓ ↓
Поток 1 Поток 2 Поток 3
диапазон диапазон диапазон
│ │ │
↓ ↓ ↓
MyList<Integer> MyList<Integer> MyList<Integer>
└────────────┼────────────┘
↓
основной поток
↓
merge
↓
int[]
```
Каждый поток создаёт **собственный контейнер `MyList<Integer>`** и записывает найденные индексы только в него.
Это позволяет избежать состояния гонки без синхронизации общей структуры.
---
# Разбиение коллекции на потоки
Количество потоков рекомендуется определять на основании:
```java
Runtime.getRuntime().availableProcessors()
```
Количество потоков не должно превышать количество элементов:
```text
threadCount = min(
availableProcessors(),
students.size()
)
```
### Пример
Для коллекции из `10000` элементов и четырёх рабочих потоков:
```text
Поток 1 → [0, 2500)
Поток 2 → [2500, 5000)
Поток 3 → [5000, 7500)
Поток 4 → [7500, 10000)
```
Каждый индекс должен принадлежать **ровно одному диапазону**.
Не допускаются:
- пропущенные индексы;
- обработка одного индекса несколькими потоками.
---
# Поиск внутри потока
Каждая задача обрабатывает только свой диапазон.
Для каждого индекса:
```java
Student student = students.get(index);
```
затем:
```java
target.equals(student)
```
Если результат `true`, индекс добавляется в локальный:
```java
MyList<Integer>
```
Пример:
```text
Поток 1 → [0, 8, 15]
Поток 2 → [21, 24]
Поток 3 → [30, 42, 48]
Поток 4 → [63]
```
---
# Объединение результатов
Основной поток после завершения всех задач должен получить локальные результаты и объединить их.
Рекомендуется передавать задачи в порядке возрастания диапазонов:
```text
Результат 1 → диапазон 0
Результат 2 → диапазон 1
Результат 3 → диапазон 2
...
```
Если каждая задача добавляет индексы в возрастающем порядке, а результаты объединяются в порядке диапазонов, итоговый массив уже будет отсортирован:
```text
[0, 8, 15, 21, 24, 30, 42, 48, 63]
```
В этом случае дополнительная сортировка результата не требуется.
**`Arrays.sort()` и другие готовые методы сортировки использовать не следует**, поскольку порядок можно гарантировать самим алгоритмом объединения.
---
# Потокобезопасность
## Мьютексы
Использование мьютексов для результата **не требуется**.
Не рекомендуется использовать:
```java
synchronized
```
```java
Lock
```
```java
ReentrantLock
```
```java
AtomicInteger
```
для общего результата.
Причина: потоки не должны изменять одну общую структуру.
Каждый поток работает со своим:
```text
MyList<Integer>
```
а объединение выполняется после завершения параллельной части одним потоком.
---
# Рекомендуемые классы и импорты
Для реализации рекомендуется использовать стандартные средства Java:
```java
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
```
Рекомендуемая схема:
```text
ExecutorService
↓
Callable<MyList<Integer>>
↓
Future<MyList<Integer>>
```
Каждый `Callable` обрабатывает свой диапазон и возвращает собственный `MyList<Integer>`.
После получения всех `Future` основной метод объединяет результаты.
После завершения работы `ExecutorService` должен быть корректно закрыт.
При `InterruptedException` необходимо восстановить статус прерывания текущего потока:
```java
Thread.currentThread().interrupt();
```
---
# Использование `MyList`
Сторонние контейнеры **не допускаются**.
Запрещено использовать:
```text
ArrayList
LinkedList
Vector
CopyOnWriteArrayList
ConcurrentLinkedQueue
и другие стандартные контейнеры
```
как хранилище промежуточных или итоговых результатов.
Для результатов каждого потока использовать:
```java
MyList<Integer>
```
Итог преобразовать в:
```java
int[]
```
---
# Требуется ли изменять `MyList`
Текущего интерфейса **достаточно для реализации задачи**:
```java
public interface MyList<T> {
void add(T element);
T get(int index);
T set(int index, T element);
T remove(int index);
int size();
boolean isEmpty();
}
```
Дополнительные методы добавлять **не требуется**.
Для задачи достаточно:
```java
size()
get(index)
add(index)
```
Поскольку поиск выполняется только на чтение, а результаты каждого потока добавляются в его собственный `MyList<Integer>`, существующего интерфейса достаточно.
### Важно
Метод:
```java
void add(T element);
```
должен добавлять элемент в конец коллекции.
Это позволяет каждому потоку формировать свои индексы в порядке их обнаружения.
---
# Использование `Student`
Изменения в `Student` **не требуются**.
Использовать существующий метод:
```java
equals(Object obj)
```
Сравнение выполнять:
```java
target.equals(students.get(index))
```
Не использовать:
```text
==
```
Не сравнивать объекты вручную по отдельным полям.
Не использовать `toString()` для определения совпадения.
---
# `StudentBuilder`
Изменения в `StudentBuilder` **не требуются**.
Он не участвует в алгоритме поиска.
---
# Обработка входных данных
Если:
```java
students == null
```
выбросить:
```java
IllegalArgumentException
```
Если:
```java
target == null
```
выбросить:
```java
IllegalArgumentException
```
Для пустой коллекции вернуть:
```java
new int[0]
```
Без создания рабочих потоков.
---
# Обобщённый алгоритм реализации
```text
1. Проверить students и target.
2. Получить размер коллекции.
3. Если размер равен 0 → вернуть [].
4. Вычислить количество потоков.
5. Разделить индексы коллекции на непересекающиеся диапазоны.
6. Создать Callable для каждого диапазона.
7. Запустить задачи через ExecutorService.
8. Каждый поток:
- получает fromIndex и toIndex;
- проходит свой диапазон;
- сравнивает Student через equals();
- записывает найденные индексы в собственный MyList<Integer>.
9. Основной поток получает результаты через Future.
10. Объединяет MyList<Integer> в порядке диапазонов.
11. Преобразует результат в int[].
12. Возвращает массив.
```
---
# Примеры тестов
## Одно совпадение
```text
[A, B, C, D]
```
Искомый:
```text
C
```
Ожидается:
```text
[2]
```
## Несколько совпадений
```text
[A, B, A, C, A]
```
Ожидается:
```text
[0, 2, 4]
```
## Нет совпадений
```text
[A, B, C]
```
Искомый:
```text
D
```
Ожидается:
```text
[]
```
## Все элементы совпадают
```text
[A, A, A, A]
```
Ожидается:
```text
[0, 1, 2, 3]
```
## Пустая коллекция
Ожидается:
```text
[]
```
## Один элемент
Проверить:
```text
[A] + A → [0]
[A] + B → []
```
## Проверка границ диапазонов потоков
Проверить коллекции размеров:
```text
1
2
3
4
10
100
1000
```
Особое внимание уделить совпадениям:
```text
на последнем элементе одного диапазона;
на первом элементе следующего диапазона.
```
Каждый индекс должен быть найден ровно один раз.
## Проверка порядка результата
Создать совпадения в разных диапазонах.
Проверить, что результат имеет вид:
```text
[0, 3, 7, 12, 25, 49, 100]
```
и находится в порядке возрастания без использования дополнительной сортировки.
## Проверка `Student.equals()`
Создать два разных экземпляра `Student` с одинаковыми значениями полей.
Один поместить в коллекцию, второй передать как `target`.
Проверить, что индекс считается совпадением согласно `equals()`.
## Проверка неизменности коллекции
После выполнения метода проверить:
- размер `MyList`;
- порядок элементов;
- значения всех элементов.
## Проверка отсутствия вывода
Перехватить стандартный вывод и убедиться, что выполнение `findOccurrences()` не выводит сообщений в консоль.
## Большая коллекция
Создать коллекцию, например из `100000` студентов, с заранее известными индексами совпадений.
Проверить:
- все ожидаемые индексы найдены;
- лишних индексов нет;
- индексы идут по возрастанию;
- итоговый массив имеет правильный размер.
## Git
Название ветки:
```text
feature/student-occurrence-index-finder
```
## Результат
Необходимо реализовать:
```text
StudentOccurrenceIndexFinder.java
```
Существующие интерфейсы и классы:
```text
MyList.java
Student.java
StudentBuilder.java
```
изменять не требуется.
+326
View File
@@ -0,0 +1,326 @@
# Техническое задание: обновление `Student`
## Задача
Обновить класс `Student` и связанный с ним код в соответствии с новой моделью данных и требованиями к неизменяемости объекта.
Текущая реализация содержит:
```java
private final String groupNumber;
private final double averageGrade;
private final String recordBookNumber;
```
Необходимо изменить тип `recordBookNumber` и добавить методы создания изменённых копий `Student`.
## Изменения в `Student`
### 1. Изменить тип `recordBookNumber`
Поле:
```java
private final String recordBookNumber;
```
заменить на:
```java
private final int recordBookNumber;
```
Метод:
```java
getRecordBookNumber()
```
должен возвращать `int`.
Все проверки, `equals()`, `hashCode()` и `toString()` должны быть адаптированы под новый тип.
### 2. Добавить бизнес-правило для `groupNumber`
`groupNumber` должен соответствовать строго следующему формату:
```text
<Заглавная буква латинского алфавита><2 цифры>
```
Регулярное выражение:
```text
^[A-Z][0-9]{2}$
```
Допустимые значения:
```text
A12
B32
Z00
```
Недопустимые значения:
```text
а12
a12
A1
A123
AA1
а1А
аа!!а123
23фи!12
№№###*()
```
Проверка должна выполняться при создании `Student`.
`Student` не может существовать с некорректным `groupNumber`.
### 3. Проверить `averageGrade`
Сохранить правило:
```text
0.0 <= averageGrade <= 5.0
```
Значения `0.0` и `5.0` допустимы.
### 4. Проверить `recordBookNumber`
После преобразования в `int` установить бизнес-правило:
```text
recordBookNumber > 0
```
Значение `0` и отрицательные значения считаются недопустимыми.
---
# Методы `with...()`
Добавить методы, возвращающие **новый объект `Student`**, не изменяя текущий объект.
Необходимо реализовать:
```java
withGroupNumber(String groupNumber)
withAverageGrade(double averageGrade)
withRecordBookNumber(int recordBookNumber)
```
Каждый метод должен:
1. Сохранять исходный объект без изменений.
2. Изменять только указанное поле в новом объекте.
3. Сохранять остальные поля исходного объекта.
4. Повторно применять все правила валидации.
5. Возвращать новый валидный объект `Student`.
Пример:
```java
Student updated = student.withAverageGrade(4.8);
```
После выполнения:
```text
student.getAverageGrade() == прежнее значение
updated.getAverageGrade() == 4.8
```
Если новое значение невалидно, метод должен выбросить исключение, а исходный объект не должен измениться.
---
# `StudentBuilder`
Обновить существующий `StudentBuilder` в соответствии с новой моделью `Student`.
Методы должны соответствовать типам:
```java
setGroupNumber(String groupNumber)
setAverageGrade(double averageGrade)
setRecordBookNumber(int recordBookNumber)
```
`build()` должен создавать только валидный `Student`.
Все новые правила валидации должны применяться при создании объекта через `StudentBuilder`.
---
# Обновление зависимого кода
Найти и обновить весь код, использующий `Student` и изменяемые методы или типы.
В частности, обновить:
- `Comparator<Student>`;
- сортировки;
- источники данных;
- тестовые классы;
- создание `Student`;
- `with...()`-операции;
- чтение и запись данных, если они используют `recordBookNumber`.
Например, существующий:
```java
implements Comparator<Student>
```
должен использовать актуальный метод:
```java
getAverageGrade()
```
вместо ошибочного `getAvarageGrade()`.
Компаратор `recordBookNumber` должен сравнивать значения как числа.
Например:
```java
Integer.compare(
s1.getRecordBookNumber(),
s2.getRecordBookNumber()
)
```
Все места, где `recordBookNumber` рассматривался как `String`, необходимо адаптировать к `int`.
---
# Тесты
Обновить существующий `StudentTest` с учётом новой модели.
## Создание корректного `Student`
Проверить создание объекта со значениями:
```text
groupNumber = A12
averageGrade = 4.5
recordBookNumber = 12345
```
Проверить корректность всех геттеров.
## Проверка `groupNumber`
Проверить успешное создание для:
```text
A12
B32
Z00
```
Проверить исключение для:
```text
a12
A1
A123
AA1
а1А
аа!!а123
23фи!12
№№###*()
```
## Проверка `averageGrade`
Проверить:
```text
0.0 — допустимо
5.0 — допустимо
-1.0 — недопустимо
6.0 — недопустимо
```
## Проверка `recordBookNumber`
Проверить:
```text
1 — допустимо
12345 — допустимо
0 — недопустимо
-1 — недопустимо
```
## Тесты `with...()`
### `withGroupNumber()`
Проверить, что:
- возвращается новый объект;
- `groupNumber` изменён;
- `averageGrade` не изменился;
- `recordBookNumber` не изменился;
- исходный `Student` остался прежним.
### `withAverageGrade()`
Проверить те же свойства для `averageGrade`.
### `withRecordBookNumber()`
Проверить те же свойства для `recordBookNumber`.
### `with...()` с невалидным значением
Для каждого метода передать некорректное значение.
Проверить:
- выбрасывается `IllegalArgumentException`;
- исходный объект не изменяется;
- не создаётся невалидный `Student`.
## `equals()` и `hashCode()`
Обновить существующие тесты с учётом `int recordBookNumber`.
Проверить:
- одинаковые студенты равны;
- одинаковые студенты имеют одинаковый `hashCode`;
- изменение любого поля создаёт отличающийся объект.
## Результат
Необходимо обновить:
```text
Student.java
StudentBuilder.java
StudentTest.java
```
а также **все классы проекта, зависящие от изменённого интерфейса `Student`**, включая компараторы и другие реализации, использующие `recordBookNumber`, `groupNumber`, `averageGrade` или их геттеры.
### Основной принцип
`Student` должен оставаться единственной точкой истины для валидности данных:
```text
StudentBuilder ──┐
├──> Student
with...() ───────┘
```
Ни `Builder`, ни `with...()`, ни внешние классы не должны позволять создать невалидный объект.
+67
View File
@@ -0,0 +1,67 @@
# Общее техническое задание: тестирование
## Задача
Для всех реализованных классов написать автоматические тесты с использованием **JUnit**.
## Требования
1. Для каждого основного класса проекта должны быть написаны тесты.
2. Проверить:
- корректное выполнение основных методов;
- корректную обработку некорректных данных;
- граничные случаи;
- ожидаемые исключения.
3. Для `Student` проверить:
- создание корректного объекта;
- невозможность создания объекта с некорректными данными;
- работу `Builder`;
- `equals()` и `hashCode()`.
4. Для `MyLinkedList` проверить:
- `add()`;
- `get()`;
- `set()`;
- `remove()`;
- `size()`;
- `isEmpty()`;
- некорректные индексы.
5. Для `MergeSortStrategy` проверить:
- сортировку по каждому из трёх полей;
- уже отсортированную коллекцию;
- обратный порядок;
- коллекцию из одного элемента;
- пустую коллекцию.
6. Для каждого компаратора проверить правильное сравнение объектов `Student`.
7. Тесты должны быть независимыми друг от друга и воспроизводимыми.
8. Необходимо использовать JUnit и стандартные средства проверки результатов.
## Результат
Тесты размещать отдельно от основного кода, например:
```text
src/
├── main/
│ └── java/
└── test/
└── java/
├── StudentTest.java
├── MyLinkedListTest.java
├── MergeSortStrategyTest.java
├── StudentGroupComparatorTest.java
├── StudentAverageGradeComparatorTest.java
└── StudentRecordBookComparatorTest.java
```
Все тесты должны успешно проходить перед слиянием ветки в `main`.