1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/wizardforcel-thinking-in-java-zh

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
В этом репозитории не указан файл с открытой лицензией (LICENSE). При использовании обратитесь к конкретному описанию проекта и его зависимостям в коде.
Клонировать/Скачать
11.3 反射:运行期类信息.md 19 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Отправлено 11.03.2025 09:15 d56454c

11.3 Рефлексия: информация о типах классов в runtime

Если точный тип объекта неизвестен, рефлексия поможет нам получить информацию о нём. Однако существует одно ограничение: тип должен быть известен во время компиляции, иначе его нельзя будет исследовать с помощью рефлексии, что затрудняет дальнейшие действия. Другими словами, компилятор должен знать заранее все классы, которые будут обрабатываться рефлексией.

На первый взгляд это может показаться незначительным ограничением, но что, если мы получаем ссылку на объект, который находится вне пространства нашего приложения? В действительности, класс объекта может быть недоступен нашему приложению даже во время компиляции. Например, представьте, что мы получили последовательность байтов с диска или через сеть, и нас проинформировали, что эти байты представляют собой какой-то класс. Поскольку компилятор не знает о данном классе во время компиляции, как тогда можно эффективно использовать этот класс?В традиционной среде программирования вероятность возникновения такой ситуации может быть невелика. Однако, когда мы переходим к более масштабному миру программирования, эта проблема требует особого внимания. Одним из первых моментов, на который следует обратить внимание, является программирование на основе компонентов. В этом контексте мы используем модель "быстрого создания приложений" (Rapid Application Development, RAD) для разработки проектов. RAD обычно реализуется в виде встроенной системы для создания приложений. Это визуальный подход к созданию программ (представленный в виде окон на экране). Можно перемещать иконки, представляющие различные компоненты, на форму. Затем настройте эти компоненты, установив их свойства или значения. Конфигурация во время дизайна требует, чтобы любой компонент был "инстанцирован" (то есть можно было бы легко получить его экземпляр). Эти компоненты также должны раскрывать часть своей информации, позволяющую программисту читать и устанавливать различные значения. Кроме того, компоненты, контролирующие события GUI, должны раскрывать информацию о методах, связанных с этими событиями, чтобы система RAD могла помочь программисту заменять эти методы событий собственным кодом. Рефлексия предоставляет специальное средство для обнаружения доступных методов и генерации их имен.JavaBeans (о которых подробно рассказывается в главе 13) предоставляют Java 1.1 основную структуру для такого программирования на основе компонентов. Одной из причин для запроса информации о классах во время выполнения заключается в создании и запуске объектов, расположенных на удалённых системах через сеть. Это называется "удалённым вызовом метода" (Remote Method Invocation, RMI) и позволяет Java-программам (версии 1.1 и выше) использовать объекты, опубликованные или распределённые несколькими машинами. Распределение таких объектов может быть вызвано различными причинами: возможно, требуется выполнить вычислительно сложную задачу, которую можно разделить между свободными машинами, чтобы ускорить процесс. В некоторых случаях может потребоваться размещение кода управления определённым типом задач (например, правил работы в многослойной клиент/серверной архитектуре) на специальной машине, которая станет общим хранилищем для этих действий. Это также позволяет легко модифицировать это хранилище, чтобы влиять на все аспекты системы (что является особенно полезным подходом, так как машина существует независимо и её программное обеспечение легко изменить!).Распределённые вычисления также могут более эффективно использовать некоторые специализированные аппаратные компоненты, которые особенно хорошо подходят для выполнения конкретных задач — например, вычисления обратной матрицы — но кажутся слишком сложными или дорогими для обычного программирования. В Java 1.1 класс Class (о котором подробнее говорилось ранее в этой главе) был расширен для поддержки концепции "рефлексии". Для классов Field, Method и Constructor (каждый из которых реализует интерфейс Member) был создан новый пакет java.lang.reflect. Объекты этих типов создаются JVM во время выполнения для представления соответствующих членов неизвестных классов. Это позволяет создавать новые объекты с помощью конструкторов, читать и изменять поля, связанные с объектами типа Field, используя методы get() и set(), а также вызывать методы, связанные с объектами типа Method, используя метод invoke(). Кроме того, мы можем использовать методы getFields(), getMethods() и getConstructors(), чтобы получить массив объектов, представляющих поля, методы и конструкторы соответственно (в онлайн-документации можно найти больше информации о классе Class). Таким образом, информация о классах анонимных объектов может быть полностью раскрыта во время выполнения, без необходимости знания чего-либо на этапе компиляции.Очень важной вещью, которую следует понимать, это то, что "рефлексия" ничего особенного собой не представляет. При работе с одним неизвестным объектом через "рефлексию", JVM просто проверяет этот объект и выясняет, к какому конкретному классу он принадлежит (то же самое, что было раньше при использовании RTTI). Однако после этого объект класса Class должен быть загружен. Поэтому файл .class для данного конкретного типа должен быть доступен для JVM (будь то локально или через сеть). Единственная разница между RTTI и "рефлексией" заключается в том, что для RTTI компилятор открывает и проверяет файл .class во время компиляции. Другими словами, мы можем вызвать все методы объекта обычным способом; но для "рефлексии" файл .class недоступен во время компиляции, а вместо этого он открывается и проверяется средой выполнения.

11.3.1 Извлечение методов классаПрямое использование инструментов рефлексии требуется крайне редко; они предоставляются в языке исключительно для поддержки других возможностей Java, таких как сериализация объектов (представленная в главе 10), Java Beans и RMI (обсуждаемые далее в этой главе). Тем не менее, нам часто требуются динамическое извлечение данных о классе и его методах. Очень полезным инструментом является извлечение методов класса. Как уже отмечалось, если просмотреть исходный код определения класса или онлайн-документацию, можно видеть только те методы, которые были определены или переопределены в этом классе, тогда как много информации из базовых классов остаётся недоступной. К счастью, "рефлексия" позволяет сделать это, и можно написать простую программу, которая автоматически выводит весь интерфейс. Вот пример такой программы:

//: ShowMethods.java
// Использование отражения в Java 1.1 для вывода всех методов класса,
// даже если эти методы определены в базовом классе.
import java.lang.reflect.*;
``````java
public class ShowMethods {
  static final String usage =
    "использование:\n" +
    "ShowMethods полное.имя.класса\n" +
    "Для вывода всех методов класса или:\n" +
    "ShowMethods полное.имя.класса слово\n" +
    "Для поиска методов, связанных с 'словом'";
  public static void main(String[] args) {
    if(args.length < 1) {
      System.out.println(usage);
      System.exit(0);
    }
    try {
      Class<?> c = Class.forName(args[0]);
      Method[] m = c.getMethods();
      Constructor<?>[] ctor = c.getConstructors();
      if(args.length == 1) {
        for (int i = 0; i < m.length; i++)
          System.out.println(m[i].toString());
        for (int i = 0; i < ctor.length; i++)
          System.out.println(ctor[i].toString());
      }
      else {
        for (int i = 0; i < m.length; i++)
          if(m[i].toString().indexOf(args[1]) != -1)
            System.out.println(m[i].toString());
        for (int i = 0; i < ctor.length; i++)
          if(ctor[i].toString().indexOf(args[1]) != -1)
            System.out.println(ctor[i].toString());
      }
    } catch (ClassNotFoundException e) {
      System.out.println("Класс не найден: " + e);
    }
  }
} ///:~

Метод getMethods() класса Class и метод getConstructors() могут вернуть массив объектов типа Method и Constructor. Каждый класс предоставляет дополнительные методы для анализа имени, аргументов и возвращаемого значения метода, который они представляют. Однако можно просто использовать метод toString(), чтобы получить строковое представление полной сигнатуры метода. Оставшаяся часть программы предназначена для извлечения информации из командной строки, проверки, совпадает ли конкретная сигнатура со строкой, которую мы ищем (используется метод indexOf()), и вывода результата. Здесь используется технология отражения, так как результат вызова Class.forName() не может быть известен во время компиляции, поэтому все данные о сигнатуре методов извлекаются во время выполнения. Изучив раздел справочной информации по теме «отражение» (reflection), можно заметить, что она предоставляет достаточно мощные средства для работы с объектами, которые были полностью неизвестны во время компиляции. Это также является примером почти автоматического процесса — Java использует эту поддержку, позволяя среде программирования контролировать Java Beans. Тем не менее, это очень интересная концепция. Один интересный эксперимент — запустить java ShowMethods ShowMethods. Это позволяет получить список, включающий публичный по умолчанию конструктор, несмотря на то что явного объявления конструктора нет в коде. Видимый конструктор сгенерирован компилятором автоматически. Если сделать класс ShowMethods непубличным (то есть «дружественным»), генерируемый по умолчанию конструктор не будет отображаться в выводе. Генерируемый по умолчанию конструктор получает ту же степень доступа, что и сам класс.

Выходные данные ShowMethods всё ещё имеют некоторые недостатки. Например, это часть вывода, полученная при вызове java ShowMethods java.lang.String:

public boolean
  java.lang.String.startsWith(java.lang.String,int)
public boolean
  java.lang.String.startsWith(java.lang.String)
public boolean
  java.lang.String.endsWith(java.lang.String)
```Удаление таких ограничителей, как `java.lang`, сделает результат более удобочитаемым. Для решения этой проблемы можно использовать класс `StreamTokenizer`, представленный в предыдущей главе:

```java
//: ShowMethodsClean.java
// ShowMethods с удаленными квалификаторами,
// чтобы сделать результаты более читаемыми
import java.lang.reflect.*;
import java.io.*;
``````java
public class ShowMethodsClean {
  static final String usage =
    "usage:\n" +
    "ShowMethodsClean fully.qualified.class.name\n" +
    "To show all methods in a class or:\n" +
    "ShowMethodsClean fully.qualified.class.name word\n" +
    "To find methods related to the word";
  public static void main(String[] args) {
    if(args.length < 1) {
      System.out.println(usage);
      System.exit(0);
    }
    try {
      Class<?> c = Class.forName(args[0]);
      Method[] m = c.getMethods();
      Constructor<?>[] ctor = c.getConstructors();
      // Convert to an array of clean strings:
      String[] n =
        new String[m.length + ctor.length];
      for(int i = 0; i < m.length; i++) {
        String s = m[i].toString();
        n[i] = StripQualifiers.strip(s);
      }
      for(int i = 0; i < ctor.length; i++) {
        String s = ctor[i].toString();
        n[i + m.length] =
          StripQualifiers.strip(s);
      }
      if(args.length == 1)
        for (int i = 0; i < n.length; i++)
          System.out.println(n[i]);
      else
        for (int i = 0; i < n.length; i++)
          if(n[i].indexOf(args[1]) != -1)
            System.out.println(n[i]);
    } catch (ClassNotFoundException e) {
      System.out.println("No such class: " + e);
    }
  }
}
Класс `StripQualifiers` очень похож на предыдущий `ShowMethods`, но он получает массивы `Method` и `Constructor` и преобразует их в одиночный массив `String`. Затем каждый такой объект `String` проходит через `StripQualifiers.strip()`, чтобы удалить все методы ограничителей. Для этого используется `StreamTokenizer` и `String`.
```Если вы забыли, есть ли у какого-то конкретного класса определённый метод, или не хотите проверять структуру класса в онлайн-документации шаг за шагом, или не знаете, может ли какой-либо объект (например, объект `Color`) выполнить определённое действие, этот инструмент поможет сэкономить много времени программирования.

Глава 17 предоставляет графическую версию этой программы, которую можно запустить во время написания кода, чтобы быстро найти нужное.

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/wizardforcel-thinking-in-java-zh.git
git@api.gitlife.ru:oschina-mirror/wizardforcel-thinking-in-java-zh.git
oschina-mirror
wizardforcel-thinking-in-java-zh
wizardforcel-thinking-in-java-zh
master