2010-01-05 8 views
5

¿Hay alguna forma práctica de referenciar un método en una clase de una manera segura? Un ejemplo básico es que si quería crear algo como la siguiente función de utilidad:Reflexión del método seguro en Java

public Result validateField(Object data, String fieldName, 
          ValidationOptions options) { ... } 

Para llamarlo, que tendría que hacer:

validateField(data, "phoneNumber", options); 

Qué me obliga a utilizar una magia cadena, o declarar una constante en algún lugar con esa cadena.

Estoy bastante seguro de que no hay forma de evitarlo con el lenguaje de stock de Java, pero ¿hay algún tipo de precompilador (de grado de producción) o compilador alternativo que pueda ofrecer una solución alternativa? (Similar a cómo AspectJ extiende el lenguaje Java) Sería bueno hacer algo como lo siguiente en su lugar:

public Result validateField(Object data, Method method, 
          ValidationOptions options) { ... } 

y llamarlo con:

validateField(data, Person.phoneNumber.getter, options); 
+0

Este es un campo común queja sobre la reflexión, y una buena razón para evitarla siempre que sea posible. Al ser alguien que tiene que reflexionar mucho sobre uno de los proyectos en los que trabajo, siento tu dolor. – aperkins

+1

Creo que el término 'seguro para tipos' es ligeramente incorrecto. usar reflection @ java es seguro para el tipo (cuando se trata de tiempo de ejecución). los errores de tipo simplemente no aparecen antes durante la compilación. –

Respuesta

4

Como otros mencionar, no es no hay forma real de hacer esto ... y no he visto un precompilador que lo soporte. La sintaxis sería interesante, por decir lo menos. Incluso en su ejemplo, solo podría cubrir un pequeño subconjunto de posibilidades potenciales de reflexión que un usuario podría querer hacer, ya que no manejará accesos no estándar o métodos que toman argumentos, etc.

Incluso si es imposible de verificar en el momento de la compilación, si desea que el código incorrecto falle lo antes posible, un enfoque es resolver los objetos del Método referenciados en el momento de la inicialización de la clase.

Imagine que tiene un método de utilidad para buscar objetos método que puede que arroja error o tiempo de ejecución excepción:

public static Method lookupMethod(Class c, String name, Class... args) { 
    // do the lookup or throw an unchecked exception of some kind with a really 
    // good error message 
} 

Luego, en sus clases, tienen constantes a preresolve los métodos que va a utilizar:

public class MyClass { 
    private static final Method GET_PHONE_NUM = MyUtils.lookupMethod(PhoneNumber.class, "getPhoneNumber"); 

    .... 

    public void someMethod() { 
     validateField(data, GET_PHONE_NUM, options); 
    } 
} 

Por lo menos, fallará tan pronto como se cargue MyClass por primera vez.

Uso mucho el reflejo, especialmente el reflejo de la propiedad de frijol, y acabo de acostumbrarme a las excepciones tardías en el tiempo de ejecución. Pero ese estilo de código de frijol tiende a error tarde para todo tipo de otras razones, siendo muy dinámico y todo. Para algo intermedio, lo anterior ayudaría.

+0

Esto parece una buena idea. Es mejor que tratar de definir constantes de cadena con los nombres de campos como lo que he visto en el código que he mantenido. – jthg

+0

Sí, es una especie de "hacer lo mejor posible". No estoy seguro de quién/por qué recibí un voto negativo de alguien. :) Siempre divertido para bajar votado sin comentarios. – PSpeed

+0

La otra cosa agradable acerca de este enfoque es cuando/si el lenguaje obtiene literales del método, entonces es concebible que sea un simple cambio para convertir. – PSpeed

3

No hay nada en el lenguaje sin embargo, - pero parte de la propuesta de cierres para Java 7 incluye literales de método, creo.

No tengo más sugerencias más allá de eso, me temo.

+0

@Downvoter: ¿te importa dar una razón? –

1

¿Hay alguna manera práctica de hacer referencia a un método en una clase de una manera segura?

En primer lugar, la reflexión es type-safe. Es solo que está tipeado dinámicamente, no está tipado estáticamente.

Por lo tanto, suponiendo que desea un estáticamente tipado equivalente a la reflexión, la respuesta teórica es que es imposible. Considere esto:

Method m; 
if (arbitraryFunction(obj)) { 
    obj.getClass().getDeclaredMethod("foo", ...); 
} else { 
    obj.getClass().getDeclaredMethod("bar", ...); 
} 

¿Podemos hacer esto para que ese tipo de tiempo de ejecución excepciones no pueden pasar? En general NO, ya que esto implicaría probar que arbitraryFunction(obj) finaliza. (Esto es equivalente al problema de detención, que se ha demostrado que es irresoluble en general, y es intratable mediante tecnología de demostración de teoremas avanzada ... AFAIK).

Y creo que este bloqueo de carreteras se aplicaría a cualquier enfoque en el que se pueda inyectar código Java arbitrario en la lógica que se usa para seleccionar reflexivamente un método de la clase de un objeto.

En mi opinión, el único enfoque moderadamente práctico en este momento sería reemplazar el código reflector con algo que genere y compile el código fuente de Java. Si este proceso ocurre antes de "ejecutar" la aplicación, ha satisfecho el requisito de seguridad de tipo estático.


estaba más preguntando por reflexión en el que el resultado es siempre el mismo. ES DECIR. Person.class.getMethod("getPhoneNumber", null) siempre devolverá el mismo método y es totalmente posible resolverlo en tiempo de compilación.

¿Qué pasa si después de compilar la clase que contiene este código, cambie Person para eliminar el método getPhoneNumber?

La única manera de estar seguro de que puede resolver getPhoneNumber reflexivamente es si de alguna manera puede evitar Person de ser cambiado. Pero no puedes hacer eso en Java. El enlace en tiempo de ejecución de las clases es una parte fundamental del lenguaje.

(para el registro, si se hizo por un método que llamó no reflexivamente, se llega a un IncompatibleClassChangeError de algún tipo cuando se cargaron las dos clases ...)

+0

Gracias por la corrección en la terminología. No estoy seguro de cómo el resto de su respuesta se relaciona con mi pregunta. Si realiza la reflexión dinámicamente en tiempo de ejecución (es decir, el resultado de la reflexión puede variar según la entrada u otro estado de tiempo de ejecución), entonces sí, es probable que no pueda asegurarse de que el código no arroje una excepción o que se detendrá. – jthg

+0

Preguntaba más sobre la reflexión en la que el resultado siempre es el mismo. ES DECIR. Person.class.getMethod ("getPhoneNumber", null) siempre devolverá el mismo método y es completamente posible resolverlo en tiempo de compilación. Del mismo modo que puede hacer Person.class para obtener un objeto Class, sería útil poder hacer algo como Person.getPhoneNumber.method para obtener un objeto Method. – jthg

0

Inspirado por burlarse de los marcos, podríamos imaginar la siguiente sintaxis:

validator.validateField(data, options).getPhoneNumber(); 
Result validationResult = validator.getResult(); 

El truco es la declaración genérica:

class Validator { 
    public <T> T validateField(T data, options) {...} 
} 

Ahora el tipo de retorno del método es el mismo que su tipo de objeto de datos y puede usar la finalización del código (y la comprobación estática) para acceder a todos los métodos, incluidos los métodos getter.

Como inconveniente, el código no es muy intuitivo de leer, ya que la llamada al captador en realidad no obtiene nada, sino que instruye al validador a validar el campo.

Otra opción posible sería para anotar los campos de la clase de datos:

class FooData { 
    @Validate(new ValidationOptions(...)) 
    private PhoneNumber phoneNumber; 
} 

Y luego a llamarlo:

FooData data; 
validator.validate(data); 

para validar todos los campos de acuerdo con las opciones comentadas.

2

Java pierde la sintaxis de azúcar para hacer algo tan bueno como Person.phoneNumber.getter. Pero si Persona es una interfaz, puede registrar el método getter utilizando un proxy dinámico. También puede grabar métodos en clases no finales utilizando CGLib, de la misma manera que lo hace Mockito.

Código
MethodSelector<Person> selector = new MethodSelector<Person>(Person.class); 
selector.select().getPhoneNumber(); 
validateField(data, selector.getMethod(), options); 

para MethodSelector: https://gist.github.com/stijnvanbael/5965609

0

El marco picklock le permite hacer lo siguiente:

class Data { 
    private PhoneNumber phoneNumber; 
} 

interface OpenData { 
    PhoneNumber getPhoneNumber(); //is mapped to the field phoneNumber 
} 

Object data = new Data(); 
PhoneNumber number = ObjectAccess 
    .unlock(data) 
    .features(OpenData.class) 
    .getPhoneNumber(); 

Esto funciona de manera parecida set y métodos privados. Por supuesto, esto es solo un envoltorio para la reflexión, pero la excepción no ocurre en el momento del desbloqueo, no en el momento de la llamada. Si lo necesita en tiempo de compilación, se podría escribir una prueba unitaria con:

assertThat(Data.class, providesFeaturesOf(OpenData.class)); 
2

Salida http://jodd.org/doc/methref.html. Utiliza la biblioteca de proxy Jodd (Proxetta) para proxy de su tipo. No estoy seguro de sus características de rendimiento, pero proporciona seguridad de tipo.

Un ejemplo: Supongamos Str.class tiene método .boo(), y que desea obtener su nombre como la cadena "boo":

Methref<Str> m = Methref.on(Str.class); 

// `.to()` returns a proxied instance of `Str` upon which you 
// can call `.boo()` Methods on this proxy are empty except when 
// you call them, the proxy stores the method's name. So doing this 
// gets the proxy to store the name `"boo"`. 

m.to().boo(); 

// You can bget the name of the method you called by using `.ref()`: 

m.ref(); // returns "boo"         

Hay más a la API que el ejemplo anterior: http://jodd.org/api/jodd/methref/Methref.html

Cuestiones relacionadas