Содержание
· 1 ИНФОРМАЦИЯ О ТИПАХ
o 1.1 Необходимость в динамическом определении типов (RTTI)
o 1.2 Объект Class
o 1.3 Литералы class
o 1.4 Параметризованные ссылки
o 1.5 Новый синтаксис преобразования
o 1.6 Проверка перед приведением типов
o 1.7 Использование литералов class
o 1.8 Динамический вызов instanceof
o 1.9 Рекурсивный подсчет
o 1.10 Регистрация фабрик
o 1.11 instanceof и сравнение Class
o 1.12 Рефлексия: динамическая информация о классе
o 1.13 Извлечение информации о методах класса
o 1.14 Динамические посредники
o 1.15 Объекты с неопределенным состоянием
o 1.16 Интерфейсы и информация о типах
o 1.17 Резюме
Механизм RTTI (Runtime Type Information) предназначен для получения и использования информации о типах во время выполнения программы. RTTI освобождает разработчика от необходимости выполнять всю работу с типами на стадии компиляции и открывает немало замечательных возможностей. Потребность в RTTI вскрывает целый ряд интересных (и зачастую сложных) аспектов объектно-ориентированного проектирования.
В этой главе рассматриваются способы получения информации об объектах и классах во время выполнения программы в Java. Существует два механизма получения такой информации: «традиционный» механизм RTTI, подразумевающий, что все типы доступны во время компиляции, а также механизм рефлексии (reflection), применяемый исключительно во время выполнения программы.
Необходимость в динамическом определении типов (RTTI)
Рассмотрим хорошо знакомый пример с геометрическими фигурами, основанный на полиморфизме. Обобщенным базовым классом является фигура Shape, а производными классами — окружность Circle, прямоугольник Square и треугольник Triangle.
Это обычная диаграмма наследования — базовый класс расположен вверху, производные классы присоединяются к нему снизу. Обычно при разработке объектно-ориентированных программ код по возможности манипулирует ссылками на базовый класс (в нашем случае это фигура — Shape). Если вдруг в программу будет добавлен новый класс (например, производный от фигуры Shape ромб — Rhomboid), то код менять не придется. В нашем случае метод draw() класса является динамически связываемым, поэтому программист-клиент может вызывать этот метод, пользуясь ссылкой базового типа Shape. Метод draw() переопределяется во всех производных классах, и по природе динамического связывания вызов его по ссылке на базовый класс все равно даст необходимый результат. Это и есть полиморфизм. Таким образом, обычно вы создаете объект конкретного класса (Circle, Square или Triangle), проводите восходящее преобразование к фигуре Shape («забывая» точный тип объекта) и используете ссылку на обобщенную фигуру. Реализация иерархии Shape может выглядеть примерно так:
</spoiler> Метод draw() базового класса Shape неявно использует метод toString() для вывода идентификатора класса, для чего ссылка this передается методу System.out.println() (обратите внимание: метод toString() объявлен абстрактным, чтобы производные классы были обязаны переопределить его и чтобы предотвратить создание экземпляров Shape). Когда этот объект встречается в выражении конкатенации строк, автоматически вызывается его метод toString() для получения соответствующего строкового представления. Каждый из производных классов переопределяет метод toString() (из базового класса Object), чтобы метод draw() выводил в каждом случае различную информацию. В данном примере восходящее преобразование происходит во время помещения объекта-фигуры в контейнерList<Shape>. В процессе восходящего преобразования теряется конкретная информация, в том числе и точный тип фигур. Контейнеру все равно — он хранит просто объекты Shape. Когда вы извлекаете из контейнера очередной элемент, контейнер, в котором все элементы хранятся в виде Object, автоматически преобразует результат обратно к Shape. Это наиболее основная форма RTTI, поскольку все подобные преобразования в языке Java проверяются на правильность на стадии исполнения. Именно для этого и служит RTTI: во время выполнения программы проверяется истинный тип объекта. В нашем случае определение типа происходит частично: тип Object преобразуется к базовому типу фигур Shape, а не к конкретным типам Circle, Square или Triangle. Просто потому, что в данный момент нам известно только то, что контейнер List<Shape> заполнен фигурами Shape. Во время компиляции соблюдение этого требования обеспечивается контейнером и системой параметризованных типов Java, а при выполнении оно подтверждается успешным преобразованием типов. Теперь в действие вступает полиморфизм — для каждой фигуры Shape вызывается свой метод draw(), в зависимости от того, окружность это (Circle), прямоугольник (Square) или треугольник (Triangle). И в основном именно так и должно быть; основная часть кода не должна зависеть от точного типа объекта, она оперирует с универсальным представлением целого семейства объектов (в нашем случае это фигура (Shape)). Такой подход упрощает написание программы, а впоследствии ее чтение и сопровождение. По этой причине полиморфизм часто используется при написании объектно-ориентированных программ. Но что, если у вас имеется не совсем обычная задача, для успешного решения которой необходимо узнать точный тип объекта, располагая только ссылкой на базовый тип? Допустим, пользователи программы с фигурами хотят выделить определенные фигуры (скажем, все треугольники) на экране фиолетовым цветом. При помощи RTTI можно узнать точный тип объекта, на который указывает ссылка базового типа Shape.