Class.getDeclaredMethod: примеры (JAVA)

Работа с объявленными методами через рефлексию
Раздел: Отражение, Методы
Class.getDeclaredMethod(String name, Class... parameterTypes): Method

Описание и сигнатура

Метод Class.getDeclaredMethod используется для получения отражения метода, объявленного прямо в данном классе. Поиск производится только по методам, определенным в самой таблице класса, без учета унаследованных методов. Возвращается объект java.lang.reflect.Method, позволяющий получить информацию о сигнатуре метода и выполнить вызов через рефлексию.

public Method getDeclaredMethod(String name, Class... parameterTypes)
        throws NoSuchMethodException, SecurityException

Аргументы:

  • name - имя метода в виде строки. При передаче null поведение приводит к исключению (см. секцию ошибок).
  • parameterTypes - список классов параметров в порядке объявления метода. Для метода без параметров передается пустой массив или ничего не передавать. Для varargs-метода указывается тип массива, например для void f(String... s) - String[].class.

Возвращаемое значение:

  • При успешном поиске возвращается экземпляр Method для запрошенного метода.
  • При отсутствии соответствия выбрасывается NoSuchMethodException.
  • При ограничениях политики безопасности может быть SecurityException. При попытке доступа к недоступному методу в среде с модульной защитой может возникнуть InaccessibleObjectException при дальнейшем использовании setAccessible(true) или вызове.

Особенности поведения:

  • Метод возвращает объявленные в классе методы любого модификатора доступа: public, protected, package-private и private.
  • Для получения унаследованных public-методов существует Class.getMethod.
  • Для группового получения всех объявленных методов применяется Class.getDeclaredMethods().

Короткие практические примеры

Примеры показывают получение и вызов методов с разными сигнатурами и обработку типичных результатов.

1) Получение и вызов public-метода без параметров

class Demo {
    public void hello() { System.out.println("hello"); }
}

// получение
Method m = Demo.class.getDeclaredMethod("hello");
// вызов
m.invoke(new Demo());
hello

2) Получение private-метода с параметрами и получение результата

class Calc {
    private int add(int a, int b) { return a + b; }
}

Method m = Calc.class.getDeclaredMethod("add", int.class, int.class);
m.setAccessible(true); // при разрешении доступа
int result = (int) m.invoke(new Calc(), 2, 3);
System.out.println(result);
5

3) Поиск varargs-метода (указывается тип массива)

class Joiner {
    public String join(String... parts) { return String.join(",", parts); }
}

Method m = Joiner.class.getDeclaredMethod("join", String[].class);
String out = (String) m.invoke(new Joiner(), (Object) new String[]{"a","b"});
System.out.println(out);
a,b

4) Ошибка при неправильном имени или сигнатуре

// если такого метода нет
Demo.class.getDeclaredMethod("noSuch");
java.lang.NoSuchMethodException: Demo.noSuch()

Альтернативы в Java и особенности

Сравнение похожих API в Java:

  • Class.getMethod - возвращает public-метод, включая унаследованные. Предпочтительнее при необходимости доступа только к публичным API.
  • Class.getDeclaredMethods - возвращает массив всех методов, объявленных в классе. Полезно для перебора и фильтрации.
  • Class.getDeclaredConstructor - аналогичный способ получения конструкторов.
  • MethodHandles.Lookup и MethodHandle - современная альтернатива для более быстрого вызова и работы с лукапами, особенно внутри модульной системы.

Выбор зависит от требований: если нужен доступ к приватным методам, то getDeclaredMethod удобен; для публичных и унаследованных методов предпочтительнее getMethod; для высокопроизводительных вызовов или доступа к приватным методам в условиях модулей - MethodHandles с приватным Lookup.

Аналоги в других языках

Краткое сравнение с отражением в популярных языках и примеры.

  • PHP: ReflectionClass::getMethod(string $name). Возвращает ReflectionMethod. Пример:
    class A { private function p() { return 'x'; } }
    $ref = new ReflectionClass('A');
    $m = $ref->getMethod('p');
    $m->setAccessible(true);
    echo $m->invoke(new A());
    x
  • JavaScript: динамический доступ к свойствам объекта. Аналога отражения на уровне класса нет, но доступ выполняется по имени: obj[methodName].call(obj, ...). Пример:
    const obj = { hello() { return 'hi' } };
    console.log(obj['hello']());
    hi
  • Python: getattr(obj, 'name') или модуль inspect. Пример:
    class A:
        def foo(self):
            return 'ok'
    
    print(getattr(A(), 'foo')())
    ok
  • C#: Type.GetMethod и BindingFlags для приватных методов. Пример:
    class A { private string P() => "x"; }
    var m = typeof(A).GetMethod("P", BindingFlags.NonPublic | BindingFlags.Instance);
    Console.WriteLine(m.Invoke(new A(), null));
    x
  • Go: пакет reflect, метод Value.MethodByName возвращает нулевое значение, если нет. Пример:
    type S struct{}
    func (S) Hello() { fmt.Println("h") }
    var v = reflect.ValueOf(S{})
    v.MethodByName("Hello").Call(nil)
    h
  • Kotlin: использует Java рефлексию или kotlin.reflect.KClass для более декларативного API. Пример с Java-рефлексией идентичен приведенным выше.
  • Lua: функции обычно хранятся в таблицах; доступ по имени осуществляется как к полю таблицы.
  • SQL: отражение методов не применимо; язык не предоставляет такого механизма.

Отличия от Java: во многих языках доступ к приватному API требует специальных флагов или недоступен вовсе; Java предоставляет подробный механизм через Method и возможность принудительного снятия ограничений, но модульная система добавила ограничения на доступ.

Типичные ошибки и их проявления

Частые проблемы при работе с getDeclaredMethod и их проявления в виде исключений.

  • NoSuchMethodException - возникает при несовпадении имени или типов параметров.
    class A { public void f(int x) {} }
    A.class.getDeclaredMethod("f"); // сигнатура не совпадает
    java.lang.NoSuchMethodException: A.f()
  • NullPointerException - возможна при передаче null в качестве имени метода или при неправильном использовании аргументов (зависит от версии JVM).
  • SecurityException - при наличии SecurityManager, запрещающем рефлексию.
  • IllegalAccessException при вызове через Method.invoke - если метод недоступен и не был открыт через setAccessible(true) или модульная система блокирует доступ.
    Method m = A.class.getDeclaredMethod("priv");
    // без setAccessible(true)
    m.invoke(a);
    java.lang.IllegalAccessException: class ... cannot access a member of class A with modifiers "private"
  • InvocationTargetException - обертка для исключения, брошенного самим вызываемым методом. Полезно распаковывать через getCause().
  • InaccessibleObjectException - в JVM с модульной системой при попытке снять ограничения через setAccessible(true) или выполнить вызов на закрытом элементе может появиться это исключение.
    Method m = Hidden.class.getDeclaredMethod("x");
    m.setAccessible(true);
    m.invoke(...);
    java.lang.reflect.InaccessibleObjectException: Unable to make ... accessible: module not open to reflection

Изменения и поведение в новых версиях Java

Важные изменения, повлиявшие на работу с рефлексией в последних релизах:

  • Введение модульной системы (Java 9) добавило ограничения на доступ к приватным классам и членам из других модулей. Прямой вызов setAccessible(true) может приводить к InaccessibleObjectException, если модуль не открыт для рефлексии.
  • Появление API MethodHandles и MethodHandle как производительного и безопасного аналога традиционной рефлексии для динамического вызова и манипуляции вызовами.
  • Поведение методов и исключений в целом стабильно; сигнатура getDeclaredMethod не менялась, но окружение безопасности и модульность стали основными факторами, влияющими на успешность доступа.

Расширенные и нетипичные сценарии применения

Несколько продвинутых примеров с пояснениями.

1) Использование getDeclaredMethod с преобразованием параметров примитивных типов и распаковкой результата

Пример java
class M {
    private static double mul(double a, double b) { return a * b; }
}

Method mm = M.class.getDeclaredMethod("mul", double.class, double.class);
mm.setAccessible(true);
Object r = mm.invoke(null, 2.5, 4.0); // static - первый аргумент null
System.out.println(r);
10.0

Пояснение: для статических методов первый аргумент у invoke равен null. Для примитивов выполняется автоупаковка/распаковка.

2) Получение MethodHandle из Method для повышения производительности

Пример java
class S { private String secret() { return "s"; } }

Method m = S.class.getDeclaredMethod("secret");
m.setAccessible(true);
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle mh = lookup.unreflect(m); // преобразование
System.out.println((String) mh.invokeExact(new S()));
s

Пояснение: преобразование через unreflect дает MethodHandle, который при частых вызовах может работать быстрее, чем Method.invoke.

3) Доступ к приватному методу из другого модуля с использованием приватного Lookup (Java 9+)

Пример java
// требуется, чтобы модуль с цельным классом открывал пакет
Class<?> target = Secret.class;
Method m = target.getDeclaredMethod("priv");
// если setAccessible запрещен, применяется privateLookupIn
MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(target, MethodHandles.lookup());
MethodHandle mh = lookup.unreflect(m);
Object out = mh.invoke(newInstanceOfSecret);
System.out.println(out);
... (результат работы метода priv)

Пояснение: privateLookupIn позволяет получить доступ в рамках модульной системы при наличии соответствующих прав или открытий модулей.

4) Поиск перегруженных методов по точной сигнатуре

Пример java
class O {
    public void p(int a) {}
    public void p(String s) {}
}
// для выбора нужной перегрузки
Method m1 = O.class.getDeclaredMethod("p", int.class);
Method m2 = O.class.getDeclaredMethod("p", String.class);
System.out.println(m1.getName() + " -> " + Arrays.toString(m1.getParameterTypes()));
System.out.println(m2.getName() + " -> " + Arrays.toString(m2.getParameterTypes()));
p -> [int]
p -> [class java.lang.String]

Пояснение: выбор перегрузки осуществляется по набору типов параметров, поэтому важно точно указывать примитивные классы (например, int.class) и массивные типы для varargs.

5) Обработка исключений, брошенных вызываемым методом

Пример java
class Ex { public void explode() { throw new RuntimeException("boom"); } }
Method m = Ex.class.getDeclaredMethod("explode");
try {
    m.invoke(new Ex());
} catch (InvocationTargetException e) {
    System.out.println(e.getCause().getMessage());
}
boom

Пояснение: исключение, брошенное внутри вызываемого метода, приходит в виде причины InvocationTargetException и требует распаковки через getCause().

джава Class.getDeclaredMethod function comments

En
Class.getDeclaredMethod Возвращает метод класса по имени и параметрам