Обработка в 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 шт):

Автор решения: Roman Konoval

Вы хоть и отфильтровали, но это только вы знаете о том, что у вас в том списке только объекты типа 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);
→ Ссылка
Автор решения: Alex Rudenko

В данной объектной модели поле типа должно скорее всего быть финальным (без сеттера) и устанавливаться при создании экземпляров конкретных дочерних классов, иначе ничто не запрещает создать экземпляр 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);
→ Ссылка