que - eventos personalizados en java
Delegados de Java? (11)
¿Tiene el lenguaje Java funciones delegadas, similar a cómo C # tiene soporte para delegados?
¿Has leído this ?
Los delegados son una construcción útil en los sistemas basados en eventos. Esencialmente, los delegados son objetos que codifican un envío de métodos en un objeto específico. Este documento muestra cómo las clases internas de Java proporcionan una solución más genérica para tales problemas.
¿Qué es un delegado? Realmente es muy similar a un puntero a la función miembro como se usa en C ++. Pero un delegado contiene el objeto de destino junto con el método que se invocará. Idealmente, sería bueno poder decir:
obj.registerHandler (ano.methodOne);
... y que el método methodOne sería llamado en ano cuando se recibiera algún evento específico.
Esto es lo que logra la estructura del Delegado.
Clases internas de Java
Se ha argumentado que Java proporciona esta funcionalidad a través de clases internas anónimas y, por lo tanto, no necesita la construcción Delegate adicional.
obj.registerHandler(new Handler() {
public void handleIt(Event ev) {
methodOne(ev);
}
} );
A primera vista, esto parece correcto pero, al mismo tiempo, una molestia. Debido a que para muchos ejemplos de procesamiento de eventos, la simplicidad de la sintaxis de los delegados es muy atractiva.
Controlador general
Sin embargo, si la programación basada en eventos se utiliza de una manera más generalizada, por ejemplo, como parte de un entorno de programación asincrónico general, hay más en juego.
En tal situación general, no es suficiente incluir solo el método de destino y la instancia del objeto de destino. En general, puede haber otros parámetros requeridos, que se determinan dentro del contexto cuando se registra el controlador de eventos.
En esta situación más general, el enfoque java puede proporcionar una solución muy elegante, particularmente cuando se combina con el uso de variables finales:
void processState(final T1 p1, final T2 dispatch) {
final int a1 = someCalculation();
m_obj.registerHandler(new Handler() {
public void handleIt(Event ev) {
dispatch.methodOne(a1, ev, p1);
}
} );
}
final * final * final
¿Tienes tu atención?
Tenga en cuenta que las variables finales son accesibles desde las definiciones de métodos de clase anónimos. Asegúrese de estudiar este código cuidadosamente para comprender las ramificaciones. Esta es una técnica potencialmente muy poderosa. Por ejemplo, se puede usar con buenos resultados al registrar manejadores en MiniDOM y en situaciones más generales.
Por el contrario, el constructo de delegado no proporciona una solución para este requisito más general y, como tal, debe rechazarse como un modismo en el que se pueden basar los diseños.
Dependiendo exactamente de lo que quiera decir, puede lograr un efecto similar (pasando por un método) utilizando el Patrón de estrategia.
En lugar de una línea como esta que declara una firma de método con nombre:
// C#
public delegate void SomeFunction();
declarar una interfaz:
// Java
public interface ISomeBehaviour {
void SomeFunction();
}
Para implementaciones concretas del método, defina una clase que implemente el comportamiento:
// Java
public class TypeABehaviour implements ISomeBehaviour {
public void SomeFunction() {
// TypeA behaviour
}
}
public class TypeBBehaviour implements ISomeBehaviour {
public void SomeFunction() {
// TypeB behaviour
}
}
Entonces, en cualquier lugar donde haya tenido un delegado SomeFunction
en C #, use una referencia ISomeBehaviour
lugar:
// C#
SomeFunction doSomething = someMethod;
doSomething();
doSomething = someOtherMethod;
doSomething();
// Java
ISomeBehaviour someBehaviour = new TypeABehaviour();
someBehaviour.SomeFunction();
someBehaviour = new TypeBBehaviour();
someBehaviour.SomeFunction();
Con las clases internas anónimas, incluso puede evitar declarar clases con nombres separados y casi tratarlas como funciones delegadas reales.
// Java
public void SomeMethod(ISomeBehaviour pSomeBehaviour) {
...
}
...
SomeMethod(new ISomeBehaviour() {
@Override
public void SomeFunction() {
// your implementation
}
});
Probablemente, esto solo debería usarse cuando la implementación sea muy específica al contexto actual y no se beneficiaría de su reutilización.
Y luego, por supuesto, en Java 8, estos se convierten básicamente en expresiones lambda:
// Java 8
SomeMethod(() -> { /* your implementation */ });
Implementé el soporte de devolución de llamada / delegado en Java utilizando la reflexión. Los detalles y la fuente de trabajo están disponibles en mi sitio web .
Cómo funciona
Hay una clase principal denominada Callback con una clase anidada llamada WithParms. La API que necesita la devolución de llamada tomará un objeto Callback como parámetro y, si es necesario, creará una Callback.WithParms como variable de método. Como gran parte de las aplicaciones de este objeto serán recursivas, esto funciona de manera muy limpia.
Dado que el rendimiento sigue siendo una gran prioridad para mí, no quería que se me pidiera crear una matriz de objetos desechables para guardar los parámetros para cada invocación; después de todo, en una gran estructura de datos podría haber miles de elementos, y en un procesamiento de mensajes Es posible que terminemos procesando miles de estructuras de datos por segundo.
Para que sea seguro en cuanto a los hilos, la matriz de parámetros debe existir de forma exclusiva para cada invocación del método de la API, y para la eficacia debe usarse la misma para cada invocación de la devolución de llamada; Necesitaba un segundo objeto que sería barato de crear para vincular la devolución de llamada con una matriz de parámetros para la invocación. Pero, en algunos escenarios, el invocador ya tendría una matriz de parámetros por otros motivos. Por estos dos motivos, la matriz de parámetros no pertenece al objeto Callback. Además, la opción de invocación (que pasa los parámetros como una matriz o como objetos individuales) pertenece a la API mediante la devolución de llamada, lo que le permite utilizar la invocación que mejor se adapte a su funcionamiento interno.
La clase anidada WithParms, entonces, es opcional y tiene dos propósitos, contiene la matriz de objetos de parámetro necesaria para las invocaciones de devolución de llamada, y proporciona 10 métodos invoke () sobrecargados (con de 1 a 10 parámetros) que cargan la matriz de parámetros y luego invocar el objetivo de devolución de llamada.
Lo que sigue es un ejemplo usando una devolución de llamada para procesar los archivos en un árbol de directorios. Este es un pase de validación inicial que solo cuenta los archivos para procesar y garantiza que ninguno exceda un tamaño máximo predeterminado. En este caso, solo creamos la devolución de llamada en línea con la invocación de la API. Sin embargo, reflejamos el método de destino como un valor estático para que la reflexión no se realice todas las veces.
static private final Method COUNT =Callback.getMethod(Xxx.class,"callback_count",true,File.class,File.class);
...
IoUtil.processDirectory(root,new Callback(this,COUNT),selector);
...
private void callback_count(File dir, File fil) {
if(fil!=null) { // file is null for processing a directory
fileTotal++;
if(fil.length()>fileSizeLimit) {
throw new Abort("Failed","File size exceeds maximum of "+TextUtil.formatNumber(fileSizeLimit)+" bytes: "+fil);
}
}
progress("Counting",dir,fileTotal);
}
IoUtil.processDirectory ():
/**
* Process a directory using callbacks. To interrupt, the callback must throw an (unchecked) exception.
* Subdirectories are processed only if the selector is null or selects the directories, and are done
* after the files in any given directory. When the callback is invoked for a directory, the file
* argument is null;
* <p>
* The callback signature is:
* <pre> void callback(File dir, File ent);</pre>
* <p>
* @return The number of files processed.
*/
static public int processDirectory(File dir, Callback cbk, FileSelector sel) {
return _processDirectory(dir,new Callback.WithParms(cbk,2),sel);
}
static private int _processDirectory(File dir, Callback.WithParms cbk, FileSelector sel) {
int cnt=0;
if(!dir.isDirectory()) {
if(sel==null || sel.accept(dir)) { cbk.invoke(dir.getParent(),dir); cnt++; }
}
else {
cbk.invoke(dir,(Object[])null);
File[] lst=(sel==null ? dir.listFiles() : dir.listFiles(sel));
if(lst!=null) {
for(int xa=0; xa<lst.length; xa++) {
File ent=lst[xa];
if(!ent.isDirectory()) {
cbk.invoke(dir,ent);
lst[xa]=null;
cnt++;
}
}
for(int xa=0; xa<lst.length; xa++) {
File ent=lst[xa];
if(ent!=null) { cnt+=_processDirectory(ent,cbk,sel); }
}
}
}
return cnt;
}
Este ejemplo ilustra la belleza de este enfoque: la lógica específica de la aplicación se abstrae en la devolución de llamada, y la monotonía de caminar recursivamente por un árbol de directorios se esconde en un método de utilidad estática completamente reutilizable. Y no tenemos que pagar repetidamente el precio de definir e implementar una interfaz para cada nuevo uso. Por supuesto, el argumento para una interfaz es que es mucho más explícito sobre qué implementar (se aplica, no se documenta simplemente), pero en la práctica no he encontrado que sea un problema obtener la definición de devolución de llamada correcta.
Definir e implementar una interfaz no es realmente tan malo (a menos que esté distribuyendo applets, como yo, donde realmente importa la creación de clases adicionales), pero donde realmente brilla es cuando tiene múltiples devoluciones de llamada en una sola clase. No solo se ven obligados a empujarlos a cada uno en una clase interna separada adicional en la aplicación implementada, sino que es francamente tedioso programar y todo ese código de placa de caldera es realmente solo "ruido".
Java no tiene delegados y está orgulloso de ello :). De lo que leí aquí encontré en esencia 2 formas de falsificar delegados: 1. reflexión; 2. clase interna
Las reflexiones son slooooow! La clase interna no cubre el caso de uso más simple: función de clasificación. No quiero entrar en detalles, pero la solución con clase interna básicamente es crear una clase contenedora para una matriz de enteros ordenados en orden ascendente y una clase para una matriz de enteros ordenados en orden descendente.
No, pero son falsificables utilizando proxies y reflexión:
public static class TestClass {
public String knockKnock() {
return "who''s there?";
}
}
private final TestClass testInstance = new TestClass();
@Test public void
can_delegate_a_single_method_interface_to_an_instance() throws Exception {
Delegator<TestClass, Callable<String>> knockKnockDelegator = Delegator.ofMethod("knockKnock")
.of(TestClass.class)
.to(Callable.class);
Callable<String> callable = knockKnockDelegator.delegateTo(testInstance);
assertThat(callable.call(), is("who''s there?"));
}
Lo bueno de este modismo es que puedes verificar que el método delegado exista, y que tenga la firma requerida, en el punto donde creas el delegador (aunque no en tiempo de compilación, desafortunadamente, aunque un complemento FindBugs podría ayuda aquí), luego úsela de forma segura para delegar en varias instancias.
Vea el código karg en github para más tests e implementation .
No, pero tiene un comportamiento similar, internamente.
En C #, los delegados se utilizan para crear un punto de entrada separado y funcionan como un puntero de función.
En Java no hay nada como puntero de función (en una vista superior) pero internamente Java necesita hacer lo mismo para lograr estos objetivos.
Por ejemplo, la creación de subprocesos en Java requiere una clase que amplíe el subproceso o que implemente Runnable, ya que una variable de objeto de clase puede usarse como un puntero de ubicación de memoria.
No, realmente no.
Puede lograr el mismo efecto utilizando el reflejo para obtener objetos del Método que luego puede invocar, y la otra manera es crear una interfaz con un solo método ''invocar'' o ''ejecutar'', y luego crear una instancia para llamar al método su interés (es decir, utilizando una clase interna anónima).
También puede encontrar este artículo interesante / útil: Un programador de Java observa delegados de C #
Sé que esta publicación es antigua, pero Java 8 ha agregado lambdas y el concepto de una interfaz funcional, que es cualquier interfaz con un solo método. Juntos, ofrecen una funcionalidad similar a los delegados de C #. Consulte aquí para obtener más información, o simplemente google Java Lambdas. http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html
Sí y No, pero el patrón delegado en Java podría pensarse de esta manera. Este video tutorial trata sobre el intercambio de datos entre actividad y fragmentos, y tiene una gran esencia del patrón sorta de delegados que usa interfaces.
Si bien no es tan limpio en ninguna parte, pero podría implementar algo así como delegados C # utilizando un Proxy Java.
Introducción
La versión más reciente del entorno de desarrollo de Microsoft Visual J ++ admite una construcción de lenguaje llamada delegados o referencias de métodos enlazados . Este constructo y las nuevas palabras clave
delegate
ymulticast
introducidas para admitirlo no son parte del lenguaje de programación Java TM , especificado por la Especificación del lenguaje Java y modificado por la Especificación de clases internas incluida en la documentación del software JDKTM 1.1. .Es poco probable que el lenguaje de programación Java alguna vez incluya este constructo. Sun ya consideró cuidadosamente adoptarlo en 1996, hasta el punto de construir y descartar prototipos en funcionamiento. Nuestra conclusión fue que las referencias de métodos vinculados son innecesarias y perjudiciales para el lenguaje. Esta decisión se tomó en consulta con Borland International, que tenía experiencia previa con referencias de métodos enlazados en Delphi Object Pascal.
Creemos que las referencias al método obligado son innecesarias porque otra alternativa de diseño, las clases internas , proporciona una funcionalidad igual o superior. En particular, las clases internas son totalmente compatibles con los requisitos del manejo de eventos de interfaz de usuario y se han utilizado para implementar una API de interfaz de usuario al menos tan completa como las clases de Windows Foundation.
Creemos que las referencias al método enlazado son perjudiciales porque desvirtúan la simplicidad del lenguaje de programación Java y el carácter orientado a objetos de las API. Las referencias de método enlazado también introducen irregularidades en la sintaxis del lenguaje y las reglas del alcance. Finalmente, diluyen la inversión en las tecnologías de VM porque se requiere que las VMs manejen eficientemente los tipos adicionales y dispares de referencias y enlaces de métodos.