09.11.2013

Рефлексия в Java - несколько примеров.

Как сообщает Википедия, рефлексия - это процесс во время которого программа может отслеживать и модифицировать свою структуру во время выполнения. Можно сказать, что программа "сама пишет" часть кода себя, прямо во время ее выполнения. Рефлексия - один из подвидов метапрограммирования - создание программ, которые в процессе своего выполнения порождают другие программы. В данном случае, какая-то часть (ну или вся) программы проектируется так, чтобы программа сама создавала во время выполнения некоторую логику, которую при "обычном" подходе создает программист. Для чего? Все очень просто - иногда некоторые данные, которые жизненно необходимы для создания алгоритма, недоступны на этапе разработки программы. Например: известно, что нужно будет вызывать определенные методы объекта, но какие именно - будет понятно только во время выполнения. Вот здесь на помощь приходит рефлексия. Впрочем, я хотел бы привести несколько самых простых примеров реализации рефлексии в Java. Давайте посмотрим на них -  станет более ясно, когда можно использовать рефлексию для какой-то конкретной ситуации.

Пример 1: создание объекта.

Допустим нужно создать копию заданного объекта. Какого конкретно типа объект не ясно, изначально мы знаем только то, что это высший тип в иерархии объектов Java - java.lang.Object. Если возможные типы объекта известны заранее, можно предусмотреть проверку с помощью оператора instanceof. Но предположим - типов много и предусмотреть проверку для каждого из них не представляется возможным, нужно универсальное решение. После создания копии объекта с ним могут выполняться какие-то действия, опять же с помощью рефлексии. Ведь мы не знаем, объект какого типа был передан - в этом и нет необходимости. Пример очень простой:

//создаем объект непосредственно того типа,
//которым является java.lang.Object source
Object newObject = source.getClass().newInstance();

Метод Class.newInstance() предполагает, что у класса, экземпляром которого является заданный объект, есть конструктор по умолчанию (без аргументов). В противном случае произойдет ошибка. Разумеется есть возможность создать класс и с помощью конструктора с аргументами. Для этого используется метод java.lang.reflect.Constructor.newInstance(). Здесь я пожалуй переадресую вас к документации Oracle - Creating New Class Instances. Итак, после создания экземпляра объекта, нужно вызвать на нем какой-то метод, который не объявлен и не определен в классе java.lang.Object, но мы то знаем - он существует (кроме того, возможно на этапе написания кода имя метода неизвестно, и определяется только во время выполнения).

Пример 2: вызов метода на объекте.

Посмотрим на метод, который позволяет вызвать на объекте любой заданный по имени метод. Если такой метод не существует, будет сгенерировано исключение NoSuchMethodException. Перехват Exception указан потому что вызов метода на объекте может порождать и другие исключения.

/**
* @param object Объект для вызова
* @param methodName Имя метода
* @param paramsTypes Типы аргументов - необязательно
* @param arg Аргументы вызываемого метода - необязательно
* @return Объект, который вернул целевой метод
*/
private Object invokeMethod(Object object, String methodName, Class<?>[] paramsTypes, Object arg) {

Object result = null;

try {

 //получаем тип объекта, на кот. вызывается метод
 Class targetObjectClass = object.getClass();

 //получаем метод класса
 Method method = findMethodAtClass(targetObjectClass, paramsTypes, methodName);

 //если есть аргумент
 if (arg != null)
 result = method.invoke(object, arg);
  else
 result = method.invoke(object);

} catch (Exception e) {
  e.printStackTrace();
}

return result;
}

Как видно, метод позволяет вызвать любой заданный по имени метод - с аргументами или без. Нужно обратить внимание на метод  findMethodAtClass.  Казалось бы, мы узнали действительный тип объекта, знаем имя метода, что еще нужно?

private Method findMethodAtClass(final Class<?> type,  Class<?>[] paramsTypes, final String methodName) throws NoSuchMethodException {

Class<?> currentType = type;
Method[] methods = type.getMethods();

//поиск метода с именем methodName в 
//заданном классе и всех его суперклассах
while (currentType != null) {
 for (Method buff : methods) {

  if (buff.getName().equals(methodName)) {

   if (paramsTypes != null) {

    Class<?>[] params = buff.getParameterTypes();
    if (Arrays.deepEquals(paramsTypes, params))
     return buff;

   } else
        if (buff.getParameterTypes().length == 0)
         return buff;
 }
}

currentType = currentType.getSuperclass();
}

throw new NoSuchMethodException();
}

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

Пример 3: доступ к полям класса.  

С помощью рефлексии можно получить доступ к любому полю класса по имени.

Можно прочитать и изменить значения приватных полей, и даже final полей. Если говорить более точно - можно "на лету" сделать подобные поля открытыми для чтения или/и изменения - таким образом можно получить доступ к полю, которое было объявлено как приватное, из любого класса.  В качестве примера - класс с приватным final полем name. Считываем значение поля, предварительно разрешив доступ к нему, а потом меняем значение:

Field privateStringField;

try {

  privateStringField = testObject.getClass().getDeclaredField("name");
  privateStringField.setAccessible(true);
  String name = (String) privateStringField.get(testObj);
  System.out.println("name = " + name);
  privateStringField.set(testObj, "Новое значение");
  name = (String) privateStringField.get(testObj);
  System.out.println("name = " + name);

  } catch (Exception e) {
    e.printStackTrace();
}

Полный код примеров



Теги: java programming

comments powered by Disqus