referenciados referencia por parametros metodos metodo ejemplos anonimo java

por - Referencia del método Java



referencia a metodos java 8 (10)

Tengo algunas clases con estos métodos:

public class TestClass { public void method1() { // this method will be used for consuming MyClass1 } public void method2() { // this method will be used for consuming MyClass2 } }

y clases:

public class MyClass1 { } public class MyClass2 { }

y quiero que HashMap<Class<?>, "question"> donde almacene (clave: clase, valor: método) pares como este (clase "tipo" está asociada con el método)

hashmp.add(Myclass1.class, "question");

y quiero saber cómo agregar referencias de métodos a HashMap (reemplazar "pregunta").

ps vengo de C # donde simplemente escribo Dictionary<Type, Action> :)


Tu pregunta

Dadas tus clases con algunos métodos:

public class MyClass1 { public void boo() { System.err.println("Boo!"); } }

y

public class MyClass2 { public void yay(final String param) { System.err.println("Yay, "+param); } }

Entonces puedes obtener los métodos a través de la reflexión:

Method method=MyClass1.class.getMethod("boo")

Al llamar a un método, necesita pasar una instancia de clase:

final MyClass1 instance1=new MyClass1(); method.invoke(instance1);

Para ponerlo juntos:

public class Main { public static void main(final String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { final Map<Class<?>,Method> methods=new HashMap<Class<?>,Method>(); methods.put(MyClass1.class,MyClass1.class.getMethod("boo")); methods.put(MyClass2.class,MyClass2.class.getMethod("yay",String.class)); final MyClass1 instance1=new MyClass1(); methods.get(MyClass1.class).invoke(instance1); final MyClass2 instance2=new MyClass2(); methods.get(MyClass2.class).invoke(instance2,"example param"); } }

Da:
¡Abucheo!
Yay, ejemplo param

Cuidado con los siguientes errores:

  • Nombre del método codificado como una cadena - esto es muy difícil de evitar
  • es una reflexión, por lo que se accede a los metadatos de la clase en tiempo de ejecución. Propenso a muchas excepciones (no se maneja en el ejemplo)
  • debe indicar no solo el nombre del método, sino también los tipos de parámetros para acceder a un método. Esto se debe a que la sobrecarga de métodos es estándar, y esta es la única manera de elegir el método de sobrecarga correcto.
  • Tenga cuidado al llamar a un método con parámetros: no hay verificación de tipo de parámetro de tiempo de compilación.

Una respuesta alternativa

Supongo que lo que estás buscando es un simple oyente: es decir, una forma de llamar a un método desde otra clase indirectamente.

public class MyClass1 implements ActionListener { @Override public void actionPerformed(final ActionEvent e) { System.err.println("Boo!"); } }

y

public class MyClass2 implements ActionListener { @Override public void actionPerformed(final ActionEvent e) { System.err.println("Yay"); } }

utilizando como:

public class Main { public static void main(final String[] args) { final MyClass1 instance1=new MyClass1(); final MyClass2 instance2=new MyClass2(); final Map<Class<?>,ActionListener> methods=new HashMap<Class<?>,ActionListener>(); methods.put(MyClass1.class,instance1); methods.put(MyClass2.class,instance2); methods.get(MyClass1.class).actionPerformed(null); methods.get(MyClass2.class).actionPerformed(null); } }

Esto se llama el patrón de escucha. Me atreví a reutilizar el ActionListener de Java Swing, pero de hecho puedes crear fácilmente tus propios oyentes declarando una interfaz con un método. MyClass1, MyClass2 implementará el método, y luego puede llamarlo como un ... método.

Sin reflexión, sin cadenas codificadas, sin desorden. (El ActionListener permite pasar un parámetro, que está sintonizado para aplicaciones GUI. En mi ejemplo, simplemente paso nulo).



Ahora que Java 8 está listo, pensé que actualizaría esta pregunta sobre cómo hacer esto en Java 8.

package com.sandbox; import java.util.HashMap; import java.util.Map; public class Sandbox { public static void main(String[] args) { Map<Class, Runnable> dict = new HashMap<>(); MyClass1 myClass1 = new MyClass1(); dict.put(MyClass1.class, myClass1::sideEffects); MyClass2 myClass2 = new MyClass2(); dict.put(MyClass2.class, myClass2::sideEffects); for (Map.Entry<Class, Runnable> classRunnableEntry : dict.entrySet()) { System.out.println("Running a method from " + classRunnableEntry.getKey().getName()); classRunnableEntry.getValue().run(); } } public static class MyClass1 { public void sideEffects() { System.out.println("MyClass1"); } } public static class MyClass2 { public void sideEffects() { System.out.println("MyClass2"); } } }


Esta es una característica que probablemente sea Java 8. Por ahora, la forma más sencilla de hacerlo es usar la reflexión.

public class TestClass { public void method(MyClass1 o) { // this method will be used for consuming MyClass1 } public void method(MyClass2 o) { // this method will be used for consuming MyClass2 } }

y llámalo usando

Method m = TestClass.class.getMethod("method", type);


Menciona en el comentario del código que cada método consume un objeto de un tipo determinado. Ya que esta es una operación común, Java ya le proporciona una interfaz funcional llamada Consumer que actúa como una forma de tomar un objeto de cierto tipo como entrada y hacer algo al respecto (dos palabras hasta ahora que ya mencionó en la pregunta). : "consumir" y "acción").

Por lo tanto, el mapa puede contener entradas donde la clave es una clase como MyClass1 y MyClass2 , y el valor es un consumidor de objetos de esa clase:

Map<Class<T>, Consumer<T>> consumersMap = new HashMap<>();

Dado que un Consumer es una interfaz funcional, es decir, una interfaz con un solo método abstracto, se puede definir utilizando una expresión lambda:

Consumer<T> consumer = t -> testClass.methodForTypeT(t);

donde testClass es una instancia de TestClass .

Dado que este lambda no hace nada más que llamar a un método existente methodForTypeT , puede usar una referencia de método directamente:

Consumer<T> consumer = testClass::methodForTypeT;

Luego, si cambia las firmas de los métodos de TestClass para que sean method1(MyClass1 obj) y method2(MyClass2 obj) , podrá agregar estas referencias de métodos al mapa:

consumersMap.put(MyClass1.class, testClass::method1); consumersMap.put(MyClass2.class, testClass::method2);


Para responder a su pregunta directa sobre el uso de un Map , las clases propuestas serían:

interface Question {} // marker interface, not needed but illustrative public class MyClass1 implements Question {} public class MyClass2 implements Question {} public class TestClass { public void method1(MyClass1 obj) { System.out.println("You called the method for MyClass1!"); } public void method2(MyClass2 obj) { System.out.println("You called the method for MyClass2!"); } }

Entonces tu Map sería:

Map<Class<? extends Question>, Consumer<Question>> map = new HashMap<>();

y poblada así:

TestClass tester = new TestClass(); map.put(MyClass1.class, o -> tester.method1((MyClass1)o)); // cast needed - see below map.put(MyClass2.class, o -> tester.method2((MyClass2)o));

y usado así:

Question question = new MyClass1(); map.get(question.getClass()).accept(question); // calls method1

Lo anterior funciona bien, pero el problema es que no hay manera de conectar el tipo de la clave del mapa con el tipo de su valor , es decir, no puede usar los genéricos para escribir correctamente el valor del consumidor y usar un método. referencia:

map.put(MyClass1.class, tester::method1); // compile error

es por eso que necesita convertir el objeto en la lambda para enlazar con el método correcto.

También hay otro problema. Si alguien crea una nueva clase de pregunta, no sabe hasta el tiempo de ejecución que no hay una entrada en el mapa para esa clase, y debe escribir código como if (!map.containsKey(question.getClass())) { // explode } para manejar esa eventualidad.

Pero hay una alternativa ...

Hay otro patrón que le da seguridad de tiempo de compilación, y significa que no necesita escribir ningún código para manejar "entradas faltantes". El patrón se llama Doble Despacho (que es parte del patrón de Visitor ).

Se parece a esto:

interface Tester { void consume(MyClass1 obj); void consume(MyClass2 obj); } interface Question { void accept(Tester tester); } public class TestClass implements Tester { public void consume(MyClass1 obj) { System.out.println("You called the method for MyClass1!"); } public void consume(MyClass2 obj) { System.out.println("You called the method for MyClass2!"); } } public class MyClass1 implements Question { // other fields and methods public void accept(Tester tester) { tester.consume(this); } } public class MyClass2 implements Question { // other fields and methods public void accept(Tester tester) { tester.consume(this); } }

Y para usarlo:

Tester tester = new TestClass(); Question question = new MyClass1(); question.accept(tester);

o para muchas preguntas:

List<Question> questions = Arrays.asList(new MyClass1(), new MyClass2()); questions.forEach(q -> q.accept(tester));

Este patrón funciona al colocar una devolución de llamada en la clase de destino, que puede vincularse con el método correcto para manejar esa clase para this objeto.

El beneficio de este patrón es que si se crea otra clase de Pregunta, se requiere implementar el método de accept(Tester) , por lo que el implementador de la Pregunta no se olvidará de implementar la devolución de llamada al Probador, y verifica automáticamente que los Probadores pueden manejar la nueva implementación , p.ej

public class MyClass3 implements Question { public void accept(Tester tester) { // Questions must implement this method tester.consume(this); // compile error if Tester can''t handle MyClass3 objects } }

También tenga en cuenta que las dos clases no se hacen referencia entre sí: solo hacen referencia a la interfaz , por lo que hay un desacoplamiento total entre las implementaciones de Tester y Question (lo que también facilita las pruebas de unidad / simulación).


Si bien puede almacenar objetos java.lang.reflect.Method en su mapa, le aconsejo que no lo haga: aún debe pasar el objeto que se utiliza como this referencia en la invocación, y el uso de cadenas en bruto para nombres de métodos puede plantear problemas en refactorización.

La forma canónica de hacer esto es extraer una interfaz (o usar una existente) y usar clases anónimas para almacenar:

map.add(MyClass1.class, new Runnable() { public void run() { MyClass1.staticMethod(); } });

Debo admitir que esto es mucho más detallado que la variante de C #, pero es la práctica común de Java, por ejemplo, cuando se realiza el manejo de eventos con escuchas. Sin embargo, otros lenguajes que se basan en la JVM suelen tener notaciones abreviadas para dichos manejadores. Al utilizar el enfoque de interfaz, su código es compatible con Groovy, Jython o JRuby y aún es seguro para los tipos.


Usando la API de reflaction

Método methodObj = TestClass.class.getMethod ("nombre del método", tipo)


Utilice interfaces en lugar de punteros de función. Defina una interfaz que defina la función a la que desea llamar y luego llame a la interfaz como se muestra en el ejemplo anterior. Para implementar la interfaz puedes usar una clase interna anónima.

void DoSomething(IQuestion param) { // ... param.question(); }


Method method = TestClass.class.getMethod("method name", type)