Эккель Брюс
Шрифт:
Метод test осуществляет проверку типов полученного объекта, используя для этого обе формы оператора instanceof. Затем он получает ссылку на объект Class и использует операцию сравнения ссылок == и метод equals, чтобы проверить объекты Class на эквивалентность. Пример доказывает справедливость утверждения о том, что действие оператора instanceof и метода islnstance одинаково. Совпадают и результаты работы операции сравнения == и метода equals. Но сами тесты приводят к разным заключениям. В соответствии с концепцией типа instanceof дает ответ на вопрос: «Объект принадлежит этому классу или производному от него?» С другой стороны, сравнение объектов Class оператором == не затрагивает наследования — либо тип точно совпадает, либо нет.
Рефлексия: динамическая информация о классе
Если вы не знаете точный тип объекта, RTTI сообщит вам его. Однако в этом случае существуют ограничения: тип должен быть известен еще во время компиляции программы, иначе определить его с помощью RTTI и сделать с этой информацией что-то полезное будет невозможно. Другими словами, компилятор должен располагать информацией обо всех классах, к которым вы затем хотели бы применить динамическое определение типов (RTTI).
Сначала кажется, что это ограничение не столь существенно, но предположим, что у вас появилась ссылка на объект, который не находится в пространстве вашей программы. Более того, класс этого объекта недоступен во время ее компиляции. Например, вы получили последовательность байтов с диска или из сетевого соединения, и вам сказали, что эта последовательность представляет некоторый класс. Но компилятор ничего не знал об этом классе, когда обрабатывал вашу программу, как же его можно использовать?
В традиционных средах программирования такая задача показалась бы далекой от реальности. Однако границы мира программирования все больше расширяются и мы все чаще встречаемся с такими ситуациями. Во-первых, такие возможности требуются для компонентного программирования, которое служит основой для систем быстрой разработки приложений (Rapid Application Development, RAD). Это визуальный подход для создания программ (экран представлен в виде «формы»), где значки, представляющие визуальные компоненты, перетаскиваются на форму. Затем происходит настройка этих компонентов, они устанавливаются в некоторое состояние во время работы программы. Чтобы изменить состояние компонентов, необходимо некоторым образом создавать их экземпляры, просматривать их содержимое, считывать и записывать внутренние значения. Вдобавок компоненты с поддержкой событий графического интерфейса должны как-то рассказать о них, чтобы система быстрой разработки приложений помогла программисту реализовать поддержку этих событий. Механизм рефлексии предоставляет средства для получения информации о доступных методах и их именах. Такое компонентное программирование поддерживается и в Java, с помощью технологии JavaBeans.
Другая важная предпосылка поддержки динамической информации о классе — предоставление возможности создавать и использовать объекты на удаленных платформах. Этот механизм, называемый удаленным вызовом методов (Remote Method Invocation, RMI), позволяет программе на Java распределять свои объекты по нескольким машинам. Необходимость в удаленном вызове методов возникает по разным причинам: например, при выполнении задачи с интенсивными вычислениями можно сбалансировать нагрузку по доступным компьютерам. Иногда код, выполняющий определенные операции, размещается на одной машине, чтобы она стала общим хранилищем этих операций и любые изменения кода на такой машине автоматически распространялись на всех клиентов этого кода. (Интересный поворот — компьютер существует исключительно для того, чтобы упростить внесение изменений в программное обеспечение!) Ко всему прочему распределенное программирование также поддерживает удаленное специализированное оборудование, которое эффективно выполняет некоторые задачи — например, обращение матриц, — которые при решении их на локальной машине могут потребовать слишком много времени и ресурсов.
Класс Class (уже описанный в этой главе) поддерживает концепцию рефлексии (reflection), для которой существует дополнительная библиотека java. lang. reflect, состоящая из классов Field, Method и Constructor (каждый реализует интерфейс Member). Объекты этих классов создаются JVM, чтобы представлять соответствующие члены неизвестного класса. Объекты Constructor используются для создания новых объектов класса, методы get и set — для чтения и записи значений полей класса, представленных объектами Field, метод invoke — для вызова метода, представленного объектом Method. Вдобавок в классе Class имеются удобные методы getFields, getMethods и getConstruc-tors, которые возвращают массивы таких объектов, как поля класса, его методы и конструкторы. (За подробной информацией обращайтесь к описанию класса Class в электронной документации JDK.) Таким образом, информация о неизвестном объекте становится доступной прямо во время выполнения программы, а потребность в ее получении ко времени компиляции программы отпадает.
Важно понимать, что в механизме рефлексии нет ничего сверхъестественного. Когда вы используете рефлексию для работы с объектом неизвестного типа, виртуальная машина JVM рассматривает его и видит, что он принадлежит определенному классу (это делает и обычное RTTI), но, перед тем как проводить с ним некоторые действия, необходимо загрузить соответствующий объект Class. Таким образом, файл .class для класса этого объекта должен быть доступен JVM либо в сети, либо в локальной системе. Таким образом, истинное различие между традиционным RTTI и рефлексией состоит в том, что при использовании RTTI файл .class открывается и анализируется компилятором. Другими словами, вы можете вызывать методы объекта «нормальным» способом. При использовании рефлексии файл .class во время компиляции недоступен; он открывается и обрабатывается системой выполнения.
Извлечение информации о методах класса
Рефлексия редко используется напрямую; она существует в языке в основном для поддержки других возможностей, таких как сериализация объектов и компоненты JavaBeans. Однако существуют ситуации, в которых динамическая информация о классе просто незаменима.
Для примера возьмем программу, выводящую на экран список методов некоторого класса. При просмотре исходного кода класса или его документации будут видны только те методы, которые были определены или переопределены именно в текущем классе. Но в классе может быть еще множество методов, доступных из его базовых классов. Искать их и сложно, и долго21. К счастью, рефлексия позволяет написать простой инструмент, выводящий полную информацию о полном интерфейсе класса. Вот как он работает:
//: typeinfo/ShowMethods.java
// Использование рефлексии для вывода полного списка методов
// класс, в том числе и определенных в базовом классе.
// {Args: ShowMethods}
import java.lang.reflect.*;
import java.util.regex.*;
import static net.mindview.util.Print.*;
public class ShowMethods {
private static String usage = "usage:\n" +
"ShowMethods qualified.class.name\n" + "To show all methods in class or:\n" + "ShowMethods qualified.class.name word\n" + "To search for methods involving 'word'"; private static Pattern p = Pattern.compile("\\w+\\."); public static void main(String[] args) { if(args.length < 1) { print(usage);