Pregunta Class.getDeclaredMethods () de reflexión comportamiento no deseado


Tengo una clase A que es una clase abstracta, la clase B es concreta y se extiende A.

Llamar a B.class.getDeclaredMethods () devuelve las firmas de métodos de la clase A además de la clase B, pero la documentación de JAVA dice algo diferente en getDeclaredMethods() 

"Esto incluye public, protected, default (package)   acceso y métodos privados, pero excluye métodos heredados ".

Así que desde arriba los documentos esperaba que el método foo () que se hereda de la clase primaria abstracta no se devuelva desde getDeclaredMethods() llamar, pero obtengo el método foo () que se hereda de la clase primaria abstracta se devuelve desde getDeclaredMethods() llamada.

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() {
    }
}

¿Puede alguien explicarme este comportamiento?

enter image description here


17
2018-04-25 10:01


origen


Respuestas:


La razón por la que obtienes esto es porque la superclase tiene acceso a nivel de paquete. Si cambias el modificador de acceso de clase A a public (tendrá que ponerlo en su propio archivo), el método adicional en B.class.getDeclaredMethods() desaparece

(También tenga en cuenta que abstract modificado en clase A es una pista falsa: lo mismo ocurre cuando la clase A no es abstracto)

Esta es una solución en el compilador de Java por un error en la reflexión: aunque foo es un método público, se definió en la clase del ámbito del paquete A. Podrías reflexionar sobre la clase B, encuentre el método, intente invocarlo utilizando la reflexión, solo para obtener un IllegalAccessException.

El compilador generará un método de puente en la clase B para que puedas invocar correctamente el método foo.


Esto se demuestra mejor si haces el método foo en A un final método, lo que hace que sea imposible solucionar este error de reflexión (no es posible anular el método)

Clases A y B están en paquete abc y clase C está en paquete def. Clase C intenta invocar el método de manera reflexiva foo en clase B que es público, pero falla porque se definió en una clase no pública A.

Excepción en el hilo "principal" java.lang.IllegalAccessException: Class   def.C no puede acceder a un miembro de la clase abc.A con modificadores "public   final"

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());
    }
}

Solo quitando el final palabra clave del método foo resuelve el problema, porque el compilador luego inserta el método de puente sintético en clase B.


Se explica en este informe de error:

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

Descripción

El siguiente programa falla en tiempo de ejecución con este error:

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  }

EVALUACIÓN

La propuesta es agregar métodos puente en estos casos muy raros para corregir   un problema en la reflexión sin ninguna otra solución o solución alternativa.   Específicamente, generaríamos un método puente cuando un método público   es heredado de una clase no pública a una clase pública.


18
2018-04-25 10:27



Por las razones enumeradas por las otras respuestas, a veces el compilador tiene que agregar algún código complicado a su archivo de clase; esto puede ser en forma de campos, constructores o métodos. Sin embargo, siempre marca esos campos como synthetic. Eso es un modificador real que agrega, y puedes verificar si el método es sintético con el método:

method.isSynthetic()

Por lo tanto, cada vez que obtenga todos los métodos, filtre su lista con este método para seleccionar solo los que realmente declaró en la fuente;)

Otros ejemplos de código sintético son: constructores por defecto que se agregan automáticamente, una referencia a la clase externa en un campo si tiene una clase interna no estática.


13
2018-04-25 10:48



La rareza no está en getDeclaredMethods() - Está en el archivo de clase para B, con un cuerpo que simplemente llama super.foo().

Yo no completamente entiéndelo, pero parece estar relacionado con foo() ser un público método declarado en una paquete-privado superclase.

Algunos casos de prueba:

  • A paquete-privado, foo público (según la pregunta): el método se genera en B
  • A paquete-privado, foo paquete-privado: el método no se genera en B
  • A público, foo público: el método no se genera en B
  • A público, foo paquete-privado: el método no se genera en B

Sospecho que la idea es que una tercera clase en un paquete diferente no puede "ver" A, pero A.foo() es todavía public, entonces debería (?) ser accesible a través de B. A fin de que hacer accesible, B necesita "redeclararlo".

No está claro para mí que esto sea realmente correcto: el (?) Anterior. Estados JLS 6.6.1 (énfasis mío):

Un miembro (clase, interfaz, campo o método) de un tipo de referencia, o un constructor de un tipo de clase, es accesible solo si el tipo es accesible y el miembro o constructor está declarado para permitir el acceso

Pero este código es permitido en un paquete diferente:

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

1
2018-04-25 10:22



Parece que has encontrado un insecto que no ha sido arreglado todavía


0
2018-04-25 10:21