Обработка в stream объектов разных типов, имеющих общего родителя
Имеется класс
public class Person {
private String name;
private Type type;
private int age;
private Gender gender;
// конструктор, сеттеры и геттеры
public enum Type {PROFESSOR, STUDENT}
public enum Gender {MALE, FEMALE}
}
и два дочерних класса
public class Teacher extends Person {
private List<String> subjects;
private List<String> groups;
// для примера
public List<String> getGroups() {
return groups;
}
// конструктор, сеттеры и остальные геттеры
}
public class Student extends Person {
private String group;
private int course;
private List<String> subjects;
// конструктор, сеттеры и геттеры
}
В классе University формирую ArrayList():
public class University {
public static List<Person> people = List.of(
new Teacher("Иванов", Person.Type.PROFESSOR, 52, Person.Gender.MALE,
List.of("Математика", "Физика", "Химия"),
List.of("Группа 1/2020", "Группа 2/2020", "Группа 3/2020")),
new Teacher("Петрова", Person.Type.PROFESSOR, 42, Person.Gender.FEMALE,
List.of("История"),
List.of("Группа 1/2020", "Группа 2/2020", "Группа 3/2020")),
new Teacher("Сидоров", Person.Type.PROFESSOR, 32, Person.Gender.MALE,
List.of("Программирование на Java", "Основы программирования"),
List.of("Группа 1/2021", "Группа 2/2021", "Группа 3/2021")),
new Student("Медведев", Person.Type.STUDENT, 17, Person.Gender.MALE,
"Группа 1/2021", 1, List.of("Математика", "Физика", "Химия")),
new Student("Рублев", Person.Type.STUDENT, 18, Person.Gender.MALE,
"Группа 1/2021", 1, List.of("Математика", "Физика", "Химия")),
new Student("Карацев", Person.Type.STUDENT, 17, Person.Gender.MALE,
"Группа 1/2020", 2, List.of("Программирование на Java", "Основы программирования")),
new Student("Хачанов", Person.Type.STUDENT, 17, Person.Gender.MALE,
"Группа 1/2020", 2, List.of("Программирование на C++", "Программирование на Паскале"))
);
}
Поскольку классы Teacher и Student унаследованы от Person, List<Person> может содержать объекты обеих дочерних классов.
При обработке потока необходимо получить список групп:
public class TestUniversity {
public static void main(String... args) {
people.stream()
.filter(p -> p.getType().equals(Person.Type.PROFESSOR))
// .map(person -> person.)
.forEach(System.out::println);
}
}
В строке, помеченной комментарием, я хочу вызвать метод getGroups(),
но он оказывается недоступным.
Если создать отдельные списки List<Teacher> и List<Student>, то все работает.
Но меня интересует именно возможность обработки потоков, содержащих разные объекты (но имеющие общего предка, т.е. они не совсем уж разные).
Возможно ли это в принципе? И если возможно, то как это реализовать?
В примере сделана попытка предварительно отфильтровать поток и хотя после фильтрации в потоке должны остаться только объекты типа Teacher, все равно метод getGroups() недоступен.
Ответы (2 шт):
Вы хоть и отфильтровали, но это только вы знаете о том, что у вас в том списке только объекты типа Teacher остались, компилятор об этом не знает никак. Нужно сделать явное приведение типа:
people.stream()
.filter(p -> p.getType().equals(Person.Type.PROFESSOR))
.map(person -> ((Teacher)person).getGroups())
.forEach(System.out::println);
Но лучше будет вообще избавиться от явного поля тип и проверять сразу тип объекта:
people.stream()
.map(p -> (p instanceof Teacher) ? (Teacher) p : null)
.filter(t -> t != null)
.map(t -> t.getGroups())
.forEach(System.out::println);
В данной объектной модели поле типа должно скорее всего быть финальным (без сеттера) и устанавливаться при создании экземпляров конкретных дочерних классов, иначе ничто не запрещает создать экземпляр Teacher с типом Person.Type.STUDENT и наоборот.
Теперь, если можно быть уверенным, что Person.Type.PROFESSOR соответствует классу Teacher, можно отфильтровать преподавателей и привести к их типу используя ссылку на метод класса Teacher.class::cast:
people.stream() // Stream<Person>
.filter(p -> Person.Type.PROFESSOR == p.getType()) // для енумов не нужен equals
.map(Teacher.class::cast) // Stream<Teacher>
.map(Teacher::getGroups)
.forEach(System.out::println);
Также существует аналог instanceof Class::isInstance:
people.stream() // Stream<Person>
.filter(Teacher.class::isInstance)
.map(Teacher.class::cast) // Stream<Teacher>
.map(Teacher::getGroups)
.forEach(System.out::println);