10 KiB
Техническое задание: заполнение MyList<Student> посредством Stream API
Задача
Реализовать механизм заполнения собственной коллекции MyList<Student> посредством Java Stream API без использования стандартных коллекций Java в качестве промежуточного или итогового хранилища.
Требования
- Итоговым результатом работы потока должна быть:
MyList<Student>
-
Для сбора элементов потока непосредственно в
MyList<Student>реализовать собственныйCollector. -
Стандартные коллекции Java (
List,ArrayList,LinkedListи другие реализацииList) запрещено использовать:- как итоговую коллекцию;
- как промежуточное хранилище элементов перед их переносом в
MyList.
-
Studentдолжен создаваться только через уже существующий независимый классStudentBuilder, если объект создаётся первоначально. -
MyListCollectorне должен создавать или изменять объектыStudent. Он работает с уже созданными объектами. -
При использовании
mapдля получения новогоStudentиспользовать методыwith...(), а неStudentBuilder. -
Логика заполнения
MyListдолжна быть отделена от логики классаStudent. -
Для добавления объектов использовать существующий метод:
add(Student student)
- Необходимо реализовать поддержку объединения частичных результатов, необходимую для работы
Collector.
MyListCollector<T>
Создать универсальный класс:
public class MyListCollector<T>
implements Collector<T, MyList<T>, MyList<T>> {
}
Реализовать методы:
supplier()
accumulator()
combiner()
finisher()
characteristics()
Назначение:
supplier()— создать пустойMyList<T>;accumulator()— добавить очередной элемент вMyList<T>;combiner()— объединить две частично заполненные коллекции;finisher()— вернуть готовыйMyList<T>;characteristics()— определить характеристики сборщика.
Коллектор должен работать с Student без специальной логики, связанной с его полями.
MyCollectors
Создать вспомогательный класс:
public final class MyCollectors {
public static <T> Collector<T, MyList<T>, MyList<T>> toMyList() {
return new MyListCollector<>();
}
}
Класс должен предоставлять удобный способ получения собственного сборщика.
Контракт использования
Должна поддерживаться конструкция:
MyList<Student> students = Stream.of(
student1,
student2,
student3
).collect(MyCollectors.toMyList());
где student1, student2, student3 — валидные объекты Student, предварительно созданные через StudentBuilder.
Результатом должен быть именно MyList<Student>.
Пример map
map должен использоваться для преобразования одного Student в новый Student.
Исходный объект не изменяется. Для создания нового объекта использовать with...().
Одно из преобразуемых полей должно быть String и формироваться по заданному бизнес-правилу:
Заглавная латинская буква + двузначное число
Например:
A07
B12
Z35
Пример:
MyList<Student> students = Stream.of(
student1,
student2,
student3
)
.map(student -> student.withGroupNumber(
generateGroupNumber(student)
))
.collect(MyCollectors.toMyList());
Пример функции формирования значения:
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 должен соответствовать:
^[A-Z][0-9]{2}$
Пример filter совместно с map
Проверить совместную работу промежуточных операций:
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>.
Реалистичный пример:
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 и собрать их:
MyList<Student> result = Stream.of(
student1,
student2,
student3
).collect(MyCollectors.toMyList());
Проверить:
result != null
result.size() == 3
и соответствие элементов исходному потоку.
Проверка порядка
Для:
student1
student2
student3
проверить:
result.get(0) == student1
result.get(1) == student2
result.get(2) == student3
Пустой поток
MyList<Student> result = Stream.<Student>empty()
.collect(MyCollectors.toMyList());
Проверить:
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...().
Результат
Реализовать:
MyListCollector.java
MyCollectors.java
Использовать существующие:
Student.java
StudentBuilder.java
MyList.java
MyLinkedList.java
Рекомендация по архитектуре
MyListCollector должен зависеть только от интерфейса MyList<T>, а не от конкретной реализации MyLinkedList<T>.
MyCollectors должен содержать только методы создания собственных сборщиков и не должен содержать бизнес-логику.