Подтвердить что ты не робот

Class.getDeclaredMethods() отражения нежелательного поведения

У меня есть класс A, который является абстрактным классом, класс B является конкретным и расширяет A.

Вызов B.class.getDeclaredMethods() возвращает подписи класса A в дополнение к классу B, но документация JAVA говорит о чем-то отличном от getDeclaredMethods()

"Это включает общедоступный, защищенный, стандартный (пакетный) доступ и частные методы, но исключает унаследованные методы".

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

import java.lang.reflect.*;

public class B extends A {
    public static void main(String[] args) throws Exception {
        Method[] methods = B.class.getDeclaredMethods();
        for (int i = 0; i < methods.length; i++) {
            System.out.println(methods[i]);
        }
    }
}


abstract class A {
    public void foo() {
    }
}

Может кто-нибудь объяснить мне такое поведение.

введите описание изображения здесь

4b9b3361

Ответ 1

Причина, по которой вы получаете это, заключается в том, что суперкласс имеет доступ к уровню пакета. Если вы измените модификатор доступа класса A на public (вам нужно будет поместить его в свой собственный файл), дополнительный метод в B.class.getDeclaredMethods() исчезнет.

(Также обратите внимание, что abstract, измененный в классе A, является красной селедкой: то же самое происходит, когда класс A не абстрактный)

Обходной путь в компиляторе Java для ошибки в отражении: хотя foo является общедоступным методом, он был определен в классе с областью охвата A. Вы можете подумать о классе B, найти метод, попытаться вызвать его с помощью отражения, только чтобы получить IllegalAccessException.

Компилятор будет генерировать метод bridge в классе B, чтобы вы могли правильно отражать метод foo.


Это лучше всего продемонстрировать, если вы сделаете метод foo в методе A a final, что делает невозможным исправить эту ошибку отражения (невозможно переопределить метод)

Классы A и B находятся в пакете abc, а класс C - в пакете def. Класс C пытается рефлексивно вызывать метод foo в классе B, который является общедоступным, но он терпит неудачу, потому что он был определен в непубличном классе A.

Исключение в потоке "main" java.lang.IllegalAccessException: Class def.C не может получить доступ к члену класса abc.A с модификаторами "public Окончательный"

package abc;

public class B extends A {
}

class A {
    public final void foo() {
    }

}
package def;

import java.lang.reflect.Method;

import abc.B;

public class C {
    public static void main(String[] args) throws Exception {
        Method m = B.class.getMethod("foo");
        m.invoke(new B());
    }
}

Просто удаление ключевого слова final из метода foo устраняет проблему, потому что компилятор затем вставляет метод синтетического моста в класс B.


В этом сообщении об ошибке объясняется:

http://bugs.java.com/view_bug.do?bug_id=6342411

Описание

Нижеприведенная программа не работает во время выполнения с этой ошибкой:

Exception in thread "main" java.lang.IllegalAccessException: Class refl.ClientTest can not access a member of class refl.a.Base with
modifiers "public"
        at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:65)
        at java.lang.reflect.Method.invoke(Method.java:578)
        at refl.ClientTest.main(ClientTest.java:9)
========== test/refl/a/Base.java ========== 
     1  package refl.a; 
     2   
     3  class Base { 
     4      public void f() { 
     5          System.out.println("Hello, world!"); 
     6      } 
     7  } 
========== test/refl/a/Pub.java ========== 
     1  package refl.a; 
     2   
     3  public class Pub extends Base {} 
========== test/refl/ClientTest.java ========== 
     1  package refl; 
     2  import refl.a.*; 
     3  import java.lang.reflect.*; 
     4   
     5  public class ClientTest { 
     6      public static void main(String[] args) throws Exception { 
     7          Pub p = new Pub(); 
     8          Method m = Pub.class.getMethod("f"); 
     9          m.invoke(p); 
    10      } 
    11  }

ОЦЕНКА

Предложение состоит в том, чтобы добавить мостовые методы в этих очень редких случаях, чтобы исправить проблема в размышлении без каких-либо других предварительных исправлений или обходных решений. В частности, мы будем генерировать метод моста, когда публичный метод наследуется от непубличного класса в открытый класс.

Ответ 2

По причинам, указанным в других ответах, иногда компилятор должен добавить в свой файл класса какой-то хитрый код; это может быть в виде полей, конструкторов или методов. Однако они всегда отмечают эти поля как synthetic. Это фактический модификатор, который он добавляет, и вы можете проверить, является ли метод синтетическим с помощью метода:

method.isSynthetic()

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

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

Ответ 3

Нечетность не находится в getDeclaredMethods() - в файле класса для B, с телом, которое просто вызывает super.foo().

Я не совсем понимаю это, но, похоже, это связано с тем, что foo() является публичным методом, объявленным в суперкомплексе private-package.

Некоторые тестовые примеры:

  • A package-private, foo public (по запросу): метод генерируется в B
  • A package-private, foo package-private: метод не генерируется в B
  • A public, foo public: метод не генерируется в B
  • A public, foo package-private: метод не генерируется в B

Я подозреваю, что идея состоит в том, что третий класс в другом пакете не может "видеть" A, но A.foo() по-прежнему public, поэтому он должен (?) быть доступен через B. Чтобы сделать его доступным, B должен "обновить" его.

Мне не ясно, что это действительно правильно - (?) выше. JLS 6.6.1 (основное внимание):

Элемент (класс, интерфейс, поле или метод) ссылочного типа или конструктор типа класса доступен , только если тип доступен, и объявлен член или конструктор разрешить доступ

Но этот код разрешен в другом пакете:

B b = new B();
b.foo();

Ответ 4

Похоже, вы нашли ошибку, которая еще не исправлена.