java 8 api objects
Class.getDeclaredMethods() de la reflexión comportamiento no deseado (4)
Tengo una clase A que es una clase abstracta, la clase B es concreta y se extiende A.
Llamar a B.class.getDeclaredMethods () devuelve firmas de métodos de clase A además de las de clase B, pero la documentación de JAVA dice algo diferente en getDeclaredMethods()
"Esto incluye el acceso público, protegido, predeterminado (paquete) y métodos privados, pero excluye los métodos heredados".
Así que de los documentos anteriores esperaba que el método foo () que se hereda de la clase padre abstracta no se devuelva de la getDeclaredMethods()
a getDeclaredMethods()
, pero getDeclaredMethods()
método foo () que se hereda de la clase padre abstracta se devuelve de la getDeclaredMethods()
a 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() {
}
}
¿Puede alguien explicarme este comportamiento?
La rareza no está en getDeclaredMethods()
; está en el archivo de clase para B
, con un cuerpo que simplemente llama a super.foo()
.
No lo entiendo completamente , pero parece estar relacionado con que foo()
es un método público declarado en una superclase de paquete privado .
Algunos casos de prueba:
-
A
paquete privado,foo
public (según pregunta): el método se genera enB
-
A
paquete privado,foo
paquete privado: el método no se genera enB
-
A
público,foo
public: el método no se genera enB
-
A
método público,foo
package-private: el método no se genera enB
Sospecho que la idea es que una tercera clase en un paquete diferente no puede "ver" A
, pero A.foo()
sigue siendo public
, por lo que debería (?) Ser accesible a través de B
Para hacerlo accesible, B
necesita "redeclararlo".
No me queda claro que esto sea realmente correcto: el (?) Anterior. JLS 6.6.1 estados (é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 está permitido en un paquete diferente:
B b = new B();
b.foo();
La razón por la que obtienes esto es porque la superclase tiene acceso a nivel de paquete. Si cambia el modificador de acceso de la clase A
a public
(deberá ponerlo en su propio archivo), el método adicional en B.class.getDeclaredMethods()
desaparecerá.
(También tenga en cuenta que el abstract
modificado en la clase A
es una pista falsa: lo mismo ocurre cuando la clase A
no es abstracta)
Esta es una solución en el compilador de Java para un error en la reflexión: aunque foo
es un método público, se definió en la clase A
ámbito del paquete. Podría reflexionar sobre la clase B
, encontrar el método, intentar invocarlo utilizando la reflexión, solo para obtener una IllegalAccessException
.
El compilador generará un método de puente en la clase B
para que pueda invocar correctamente el método foo
.
Esto se demuestra mejor si hace que el método foo
en A
un método final
, lo que hace que sea imposible corregir este error de reflexión (no es posible anular el método)
Las clases A
y B
están en el paquete abc
y la clase C
está en el paquete def
. La clase C
intenta invocar reflexivamente el método foo
en la clase B
que es pública, pero falla porque se definió en la clase A
no pública.
Excepción en el hilo "main" java.lang.IllegalAccessException: la clase 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());
}
}
El solo hecho de eliminar la palabra clave final
del método foo
resuelve el problema, ya que el compilador inserta el método de puente sintético en la 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 de puente en estos casos muy raros para solucionar un problema en la reflexión sin ninguna otra solución o solución provisional prevista. Específicamente, generaríamos un método puente cuando un método público se hereda de una clase no pública en una clase pública.
Parece que has encontrado un error que aún no se ha solucionado.
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()
Entonces, cuando 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 predeterminados que se agregan automáticamente, una referencia a la clase externa en un campo si tiene una clase interna no estática.