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

Есть ли способ получить список методов, которые будут доступны (не обязательно общедоступны) определенным классом? Этот код будет находиться в совершенно другом классе.

Пример:

public class A {
  public void methodA1();
  protected void methodA2();
  void methodA3();
  private void methodA4();
}

public class B extends A {
  public void methodB1();
  protected void methodB2();
  private void methodB3();
}

Для класса B я хотел бы получить:

  • все свои методы
  • methodA1 и methodA2 из класса A
  • methodA3 тогда и только тогда, когда класс B находится в том же пакете, что и A

methodA4 никогда не должен включаться в результаты, поскольку он недоступен классу B. Чтобы еще раз прояснить, код, который должен найти и вернуть указанные выше методы, будет в совершенно другом классе/пакете.

Теперь Class.getMethods() возвращает только общедоступные методы и, следовательно, не будет делать то, что я хочу; Class.getDeclaredMethods() возвращает методы только для текущего класса. Хотя я могу, конечно, использовать последнее и пройти иерархию классов, проверяя правила видимости вручную, я бы предпочел, чтобы там было лучшее решение. Я пропустил что-то очевидное здесь?

Ответы

Ответ 1

Используйте Class.getDeclaredMethods(), чтобы получить список всех методов (частных или других) из класса или интерфейса.

Class c = ob.getClass();
for (Method method : c.getDeclaredMethods()) {
  if (method.getAnnotation(PostConstruct.class) != null) {
    System.out.println(method.getName());
  }
}

Примечание: это исключает унаследованные методы. Для этого используйте Class.getMethods(). Он вернет все общедоступные методы (унаследованные или нет).

Чтобы сделать полный список всего, к чему может получить доступ класс (включая унаследованные методы), вам нужно пройти через дерево классов, которое оно расширяет. Итак:

Class c = ob.getClass();
for (Class c = ob.getClass(); c != null; c = c.getSuperclass()) {
  for (Method method : c.getDeclaredMethods()) {
    if (method.getAnnotation(PostConstruct.class) != null) {
      System.out.println(c.getName() + "." + method.getName());
    }
  }
}

Ответ 2

Поскольку cletus и PSpeed ​​уже ответили - вам нужно пройти дерево наследования классов.

Так я это делаю, но без обработки частных методов пакета:

public static Method[] getAccessibleMethods(Class clazz) {
   List<Method> result = new ArrayList<Method>();

   while (clazz != null) {
      for (Method method : clazz.getDeclaredMethods()) {
         int modifiers = method.getModifiers();
         if (Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers)) {
            result.add(method);
         }
      }
      clazz = clazz.getSuperclass();
   }

   return result.toArray(new Method[result.size()]);
}

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

Ответ 3

Довольно уверен, что вам придется подойти к суперклассам, чтобы получить то, что вы хотите. В конце концов, то, что getMethods() делает с вызовом getDeclaredMethods() внутренне (вроде: он фактически вызывает приватную версию, которая отфильтровывает непубличные методы, но она переводит дерево классов для построения полного списка).

Любопытно, почему такая вещь нужна.

Ответ 4

Точка ответа Cletus (я не могу комментировать там, потому что у меня недостаточно репутации.). Во всяком случае, код Cletus не работал у меня (Eclipse также жаловался на это), вероятно, из-за изменений в Java с 2009 года.

Строка:

for (Class c = ob.getClass(); c != null; c = c.getSuperclass()) {

пришлось изменить на:

for (Class<?> c = ob.getClass(); c != null; c = c.getSuperclass()) {

чтобы получить какой-либо вывод. Таким образом, полный код для меня был (включая типы входных аргументов, модификаторы и тип возвращаемого значения):

    for (Class<?> c = scanner.getClass(); c != null; c = c.getSuperclass()) {
      System.out.println(c.getName());          
      for (Method method : c.getMethods()) {
          System.out.println("\t" + Modifier.toString(method.getModifiers()) 
            + " " + method.getName());
          for (Class<?> param: method.getParameterTypes()) {
              System.out.println("\t\t" + param.getName());
          }
          System.out.println("\t\t == returns ==> " + method.getReturnType().getName());
      }
    }