Рейтинговые книги
Читем онлайн Философия Java3 - Брюс Эккель

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 75 76 77 78 79 80 81 82 83 ... 132

Класс 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.namen" + "To show all methods in class or:n" + "ShowMethods qualified.class.name wordn" + "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);

System exit(O);

}

int lines = 0; try {

Class<?> с = Class.forName(args[0]); Method[] methods = c.getMethods(); Constructor[] ctors = c.getConstructorsO; if(args.length == 1) {

for(Method method . methods) print(

p.matcher(method.toStri ng()).replaceAl

1С"));

for(Constructor ctor • ctors)

pri nt(p.matcher(ctor.toStri ng()).replaceAl1("")

);

lines = methods.length + ctors.length,

} else {

for(Method method : methods)

if (method. toStringO. indexOf(args[l]) != -1) { print(

p.matcher(method.toStri ng О)

replaceAll (""));

lines++;

}

for(Constructor ctor : ctors)

if(ctor.toStringO.indexOf(args[l]) != -1) { print(p.matcher(

ctor.toStri ng О).replaceAl 1 ("

lines++;

}

}

} catch(ClassNotFoundException e) {

print("No such class: " + e):

}

}

} /* Output:

public static void main(String[]) public native int hashCodeO public final native Class getClassO

public final void wait(long.int) throws InterruptedException public final void waitO throws InterruptedException public final native void wait(long) throws InterruptedException public boolean equals(Object) public String toStringO public final native void notifyО public final native void notifyAllO public ShowMethodsO *///:-

Методы класса Class getMethods() и getConstructors() возвращают массивы объектов Method и Constructor, которые представляют методы и конструкторы класса. В каждом из этих классов есть методы для получения и анализа имен, аргументов и возвращаемых значений представляемых методов и конструкторов. Впрочем, также можно использовать простой метод toString(), как и сделано здесь, чтобы получить строку с полным именем метода. Остальная часть кода разбирает командную строку и определяет, подходит ли определенное выражение образцу для поиска (с использованием indexOf()), а после выделяет описатели имен классов.

Результат, полученный от Class.forName(), не может быть известен во время компиляции, поэтому вся информация о сигнатуре методов становится доступной во время выполнения. Если вы тщательно изучите документацию по рефлексии из JDK, то увидите, что рефлексия позволяет установить необходимые аргументы и вызвать метод объекта, «абсолютно неизвестного» во время компиляции программы (чуть позже будут приведены соответствующие примеры). Скорее всего, вам эти возможности никогда не понадобятся, но сам факт их существования интересен.

Приведенный выше результат был получен из командной строки

java ShowMethods ShowMethods

На экран выводится открытый (public) конструктор по умолчанию, хотя в тексте программы такой конструктор не определяется. Тот конструктор, что имеется теперь в классе, автоматически сгенерирован компилятором. Если вы после этого сделаете класс ShowMethods не открытым (удалите из его определения спецификатор доступа public, то есть предоставите ему доступ в пределах пакета), сгенерированный компилятором конструктор исчезнет из списка методов. Сгенерированный конструктор имеет тот же уровень доступа, что и его класс.

Также интересно запустить программу в виде

java ShowMethods java lang String

с передачей дополнительного параметра char, int, String и т. п.

Эта программа сэкономит вам немало времени при программировании, когда вы будете мучительно вспоминать, есть ли у этого класса определенный метод, если вам потребуется узнать, имеются ли у некоторого класса методы, возвращающие объекты Color, и т. д.

Динамические посредники

«Посредник» (proxy) принадлежит к числу основных паттернов проектирования. Он представляет собой объект, который подставляется на место «настоящего» объекта для расширения или модификации его операций. Приведу тривиальный пример, показывающий структуру посредника:

//. typeinfo/SimpleProxyDemo java import static net mindview.util Print *,

interface Interface { void doSomethingO; void somethingElse(String arg).

}

class Real Object implements Interface {

public void doSomethingO { printC'doSomething"); } public void somethingElse(String arg) { printC'somethingElse " + arg),

}

}

class SimpleProxy implements Interface { private Interface proxied, public SimpleProxy(Interface proxied) { this.proxied = proxied,

}

public void doSomethingO {

print("SimpleProxy doSomething"), proxied doSomethingO,

}

public void somethingElse(String arg) {

print("SimpleProxy somethingElse " + arg); proxied.somethingElse(arg);

}

class SimpleProxyDemo {

public static void consumer^Interface iface) { iface doSomethingO; iface somethingElseC'bonobo");

}

public static void main(String[] args) { consumer(new RealObjectO), consumer(new SimpleProxy(new RealObjectO)),

}

} /* Output doSomething somethingElse bonobo SimpleProxy doSomething doSomething

SimpleProxy somethingElse bonobo

somethingElse bonobo */// ~

Поскольку consumer() получает Interface, он не знает, что ему передается — «настоящий» объект (RealObject) или посредник (Proxy), потому что оба типа реализуют Interface. Объект Proxy, находящийся между клиентом и «настоящим» объектом, выполняет операции, а затем вызывает идентичные методы RealObject.

Посредник пригодится в любой ситуации, когда требуется отделить дополнительные операции от «настоящего» объекта, и особенно когда нужно легко переключаться из режима использования дополнительных операций в режим отказа от них (и наоборот — главной целью паттернов является инкапсуляция изменений, поэтому для оправдания их применения что-то должно изменяться). Допустим, вы хотите отслеживать вызовы методов RealObject, измерять затраты на эти вызовы, и т. д. Такой код не должен встраиваться в приложение, а посредник позволит легко добавить или убрать его по мере необходимости.

Динамические посредники Java развивают концепцию посредника — и объект посредника создается динамически, и обработка вызовов опосредованных методов тоже осуществляется динамически. Все вызовы, обращенные к динамическому посреднику, перенаправляются одному обработчику, который определяет, что это за вызов и как с ним следует поступить. Вот как выглядит пример SimpleProxyDemo.java, переписанный для динамического посредника:

// typeinfo/SimpleDynamicProxy java import java lang.reflect *.

class DynamicProxyHandler implements InvocationHandler { private Object proxied; public DynamicProxyHandler(Object proxied) { this proxied = proxied,

}

public Object

invoke(Object proxy, Method method, Object[] args) throws Throwable {

System out.printlnC'**** proxy. " + proxy.getClass() +

method- " + method + ", args " + args); if(args != nul 1) продолжение &

for(Object arg : args)

System.out.println(" " + arg); return method.invoke(proxied, args);

}

}

class SimpleDynamicProxy {

public static void consumer(Interface iface) { iface.doSomething(); i face.somethi ngElse("bonobo");

}

public static void main(String[] args) {

Real Object real = new Real ObjectО; consumer(real);

// Вставляем посредника и вызываем снова: Interface proxy = (Interface)Proxy.newProxyInstance( Interface.class.getClassLoader(), new Class[]{ Interface.class }. new DynamicProxyHandler(real)); consumer(proxy);

1 ... 75 76 77 78 79 80 81 82 83 ... 132
На этой странице вы можете бесплатно читать книгу Философия Java3 - Брюс Эккель бесплатно.
Похожие на Философия Java3 - Брюс Эккель книги

Оставить комментарий