Delegados de Java?


194

¿El lenguaje Java tiene características de delegado, similar a cómo C # tiene soporte para delegados?


20
@Suma ¿Cómo puede ser un duplicado si la pregunta que mencionaste fue publicada un año después de esta?
Ani

1
Java 8 tiene una característica bastante parecida a los delegados. Se llama lambdas.
tbodt

44
@tbodt para ser más correcto, Java 8 tiene una característica bastante similar a los delegados. Se llama interfaces funcionales . Las lambdas son una forma de crear tales instancias de delegado (anónimamente).
nawfal

3
Las respuestas a continuación son de Pre-Java 8. Después de Java 8, vea las respuestas en este hilo: stackoverflow.com/questions/20311779/…
Michael Lloyd Lee mlk

@nawfal, +1, pero para ser aún más correcto, Java 7 y versiones anteriores ya tienen una característica similar a los delegados. Se llama interfaces simples.
Pacerier

Respuestas:


152

No, realmente no.

Es posible que pueda lograr el mismo efecto utilizando la reflexión para obtener los objetos del Método que luego puede invocar, y la otra forma es crear una interfaz con un solo método 'invocar' o 'ejecutar', y luego instanciarlos para llamar al método le interesa (es decir, usar una clase interna anónima).

También puede encontrar este artículo interesante / útil: un programador de Java mira a los delegados de C # (@ archive.org)


3
La solución con invoke () como la única función miembro de una interfaz es realmente agradable.
Stephane Rolland

1
Pero vea mi ejemplo aquí de lo que uno haría, incluso en Java 7, para lograr el equivalente a un delegado de C #.
ToolmakerSteve

63

Dependiendo exactamente de lo que quiere decir, puede lograr un efecto similar (pasar un método) usando 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
   }
}

Luego, donde haya tenido un SomeFunctiondelegado en C #, use una ISomeBehaviourreferencia en su lugar:

// C#
SomeFunction doSomething = SomeMethod;
doSomething();
doSomething = SomeOtherMethod;
doSomething();

// Java
ISomeBehaviour someBehaviour = new TypeABehaviour();
someBehaviour.SomeFunction();
someBehaviour = new TypeBBehaviour();
someBehaviour.SomeFunction();

Con clases internas anónimas, incluso puede evitar declarar clases con nombre separadas y casi tratarlas como funciones de delegado reales.

// Java
public void SomeMethod(ISomeBehaviour pSomeBehaviour) {
   ...
}

...

SomeMethod(new ISomeBehaviour() { 
   @Override
   public void SomeFunction() {
      // your implementation
   }
});

Esto probablemente solo debería usarse cuando la implementación es muy específica para el contexto actual y no se beneficiaría de su reutilización.

Y luego, por supuesto, en Java 8, se convierten básicamente en expresiones lambda:

// Java 8
SomeMethod(() -> { /* your implementation */ });

66
+1. esta es una solución decente, y la verbosidad puede compensarse con mantenibilidad en el futuro.
nawfal

esto es genial ... Actualmente estoy trabajando en un proyecto donde no puedo usar la reflexión debido a las limitaciones del proyecto y esta solución hace el trabajo maravillosamente :)
Jonathan Camarena

¿Java tiene una convención de nomenclatura de prefijo I para las interfaces? No he visto eso antes
Kyle Delaney

36

Cuento corto: no .

Introducción

La versión más reciente del entorno de desarrollo Microsoft Visual J ++ admite una construcción de lenguaje llamada delegados o referencias de métodos enlazados . Esta construcción, y las nuevas palabras clave delegatee multicastintroducidas para admitirla, no forman parte del lenguaje de programación Java TM , que está especificado por la Especificación del lenguaje Java y modificado por la Especificación de clases internas incluida en la documentación para el software JDKTM 1.1 .

Es poco probable que el lenguaje de programación Java incluya alguna vez esta construcción. Sun ya consideró cuidadosamente adoptarlo en 1996, hasta el punto de construir y descartar prototipos funcionales. 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 vinculados en Delphi Object Pascal.

Creemos que las referencias de métodos vinculados 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 la 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 de métodos vinculados son perjudiciales porque restan valor a la simplicidad del lenguaje de programación Java y al carácter generalizado orientado a objetos de las API. Las referencias de métodos enlazados también introducen irregularidades en la sintaxis del lenguaje y las reglas de alcance. Finalmente, diluyen la inversión en tecnologías VM porque se requiere que las VM manejen tipos de referencias adicionales y enlaces de métodos diferentes de manera eficiente.


Como dice en qué Patrick se vinculó , desea usar clases internas en su lugar.
SCdF

16
Excelente artículo. ME ENCANTA su definición de "simple": Java está diseñado para ser un lenguaje simple como en "Java tiene que ser simple para compilar / VM para" mientras ignora "Java tiene que ser simple para escribir / leer por una persona" . Explica mucho
Juozas Kontvainis

55
Solo creo que SUN cometió un gran error. Simplemente no han sido convencidos por el paradigma funcional, y eso es todo.
Stephane Rolland

77
@Juozas: Python se hace explícitamente para que sea simple de escribir / leer por una persona y sí implementa funciones / delegados lambda .
Stephane Rolland

55
Reemplazado con un enlace archive.org. Además, eso es realmente estúpido, Oracle.
Patrick

18

¿Has leído esto ?

Los delegados son una construcción útil en sistemas basados ​​en eventos. Esencialmente, los Delegados son objetos que codifican un envío de método en un objeto especificado. Este documento muestra cómo las clases internas de Java proporcionan una solución más genérica a 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 a invocar. Idealmente, sería bueno poder decir:

obj.registerHandler (ano.methodOne);

..y que el método methodOne se invocaría en ano cuando se recibiera algún evento específico.

Esto es lo que logra la estructura de 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 Delegado adicional.

obj.registerHandler(new Handler() {
        public void handleIt(Event ev) {
            methodOne(ev);
        }
      } );

A primera vista, esto parece correcto pero al mismo tiempo una molestia. Porque para muchos ejemplos de procesamiento de eventos, la simplicidad de la sintaxis de Delegados es muy atractiva.

Manejador general

Sin embargo, si la programación basada en eventos se usa de manera más generalizada, por ejemplo, como parte de un entorno general de programación asincrónica, hay más en juego.

En una situación tan 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 necesarios, que se determinan dentro del contexto cuando se registra el controlador de eventos.

En esta situación más general, el enfoque de 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étodo de clase anónima. Asegúrese de estudiar este código cuidadosamente para comprender las ramificaciones. Esta es potencialmente una técnica muy poderosa. Por ejemplo, se puede utilizar con buenos resultados al registrar controladores en MiniDOM y en situaciones más generales.

Por el contrario, el constructo Delegado no proporciona una solución para este requisito más general y, como tal, debe rechazarse como un idioma en el que se pueden basar los diseños.



5

No, pero son falsificables usando proxies y reflejos:

  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 idioma es que puede verificar que el método delegado a exista y que tenga la firma requerida, en el punto donde crea el delegador (aunque no en tiempo de compilación, desafortunadamente, aunque un complemento FindBugs podría ayuda aquí), luego úselo de manera segura para delegar en varias instancias.

Vea el código karg en github para más pruebas e implementación .


2

He implementado el soporte de devolución de llamada / delegado en Java usando la reflexión. Los detalles y la fuente de trabajo están disponibles en mi sitio web .

Cómo funciona

Hay una clase principal llamada Callback con una clase anidada llamada WithParms. La API que necesita la devolución de llamada tomará un objeto de devolución de llamada como parámetro y, si es necesario, creará un Callback.WithParms como una variable de método. Dado que muchas 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 que creara una matriz de objetos desechables para contener los parámetros para cada invocación; después de todo, en una estructura de datos de gran tamaño podría haber miles de elementos y un procesamiento de mensajes escenario podríamos terminar procesando miles de estructuras de datos por segundo.

Para ser seguro para subprocesos, la matriz de parámetros debe existir de manera única para cada invocación del método API, y para mayor eficacia, se debe usar el mismo 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 elección de la invocación (pasando los parámetros como una matriz o como objetos individuales) pertenece a la API utilizando la devolución de llamada 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ámetros necesarios para las invocaciones de devolución de llamada, y proporciona 10 métodos invoke () sobrecargados (con 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 a procesar y garantiza que ninguno exceda un tamaño máximo predeterminado. En este caso, simplemente 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 siempre.

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 el trabajo pesado de recorrer recursivamente un árbol de directorios está bien escondido 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 simplemente se documenta), pero en la práctica no he encontrado que sea un problema obtener la definición correcta de devolución de llamada.

Definir e implementar una interfaz no es realmente tan malo (a menos que esté distribuyendo applets, como yo lo hago, donde evitar crear clases adicionales realmente importa), pero donde esto realmente brilla es cuando tiene múltiples devoluciones de llamada en una sola clase. No solo se está forzando a empujarlos a cada uno en una clase interna separada agregada sobrecarga en la aplicación implementada, sino que es francamente tedioso programar y todo ese código de placa de caldera es realmente solo "ruido".


1

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 fragmentos de actividad, y tiene una gran esencia de delegar un patrón mediante interfaces.

Interfaz Java


1

No tiene una delegatepalabra clave explícita como C #, pero puede lograr algo similar en Java 8 utilizando una interfaz funcional (es decir, cualquier interfaz con exactamente un método) y lambda:

private interface SingleFunc {
    void printMe();
}

public static void main(String[] args) {
    SingleFunc sf = () -> {
        System.out.println("Hello, I am a simple single func.");
    };
    SingleFunc sfComplex = () -> {
        System.out.println("Hello, I am a COMPLEX single func.");
    };
    delegate(sf);
    delegate(sfComplex);
}

private static void delegate(SingleFunc f) {
    f.printMe();
}

Cada nuevo objeto de tipo SingleFuncdebe implementarse printMe(), por lo que es seguro pasarlo a otro método (por ejemplo delegate(SingleFunc)) para llamar al printMe()método.


0

Si bien no es tan limpio, podría implementar algo como delegados de C # usando un Proxy Java .


2
Si bien este enlace puede responder la pregunta, es mejor incluir las partes esenciales de la respuesta aquí y proporcionar el enlace como referencia. Las respuestas de solo enlace pueden volverse inválidas si la página vinculada cambia.
StackFlowed

2
@StackFlowed Elimina el enlace y ¿qué obtienes? Una publicación con información. Vota para mantener.
Scimonster

1
@Scimonster, ¿qué pasaría si el enlace ya no es válido?
StackFlowed

@StackFlowed Todavía proporciona una respuesta: use un Proxy Java.
Scimonster

entonces podría dejarse como un comentario?
StackFlowed

0

No, pero tiene un comportamiento similar, internamente.

En C #, los delegados se utilizan para crear un punto de entrada separado y funcionan de manera muy similar a 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 implemente Runnable, porque una variable de objeto de clase se puede usar como un puntero de ubicación de memoria.



0

El código descrito ofrece muchas de las ventajas de los delegados de C #. Los métodos, ya sea estáticos o dinámicos, pueden tratarse de manera uniforme. La complejidad en los métodos de llamada a través de la reflexión se reduce y el código es reutilizable, en el sentido de que no requiere clases adicionales en el código del usuario. Tenga en cuenta que estamos llamando a una versión alternativa de invoke, donde se puede llamar a un método con un parámetro sin crear una matriz de objetos. Código Java a continuación:

  class Class1 {
        public void show(String s) { System.out.println(s); }
    }

    class Class2 {
        public void display(String s) { System.out.println(s); }
    }

    // allows static method as well
    class Class3 {
        public static void staticDisplay(String s) { System.out.println(s); }
    }

    public class TestDelegate  {
        public static final Class[] OUTPUT_ARGS = { String.class };
        public final Delegator DO_SHOW = new Delegator(OUTPUT_ARGS,Void.TYPE);

        public void main(String[] args)  {
            Delegate[] items = new Delegate[3];

            items[0] = DO_SHOW .build(new Class1(),"show,);
            items[1] = DO_SHOW.build (new Class2(),"display");
            items[2] = DO_SHOW.build(Class3.class, "staticDisplay");

            for(int i = 0; i < items.length; i++) {
                items[i].invoke("Hello World");
            }
        }
    }

-11

Java no tiene delegados y está orgulloso de ello :). Por lo que leí aquí, encontré en esencia 2 formas de falsificar delegados: 1. reflexión; 2. clase interna

¡Las reflexiones son deslumbrantes! La clase interna no cubre el caso de uso más simple: la función de clasificación. No quiero entrar en detalles, pero la solución con la clase interna básicamente es crear una clase de contenedor para una matriz de enteros que se ordenarán en orden ascendente y una clase para una matriz de enteros que se ordenarán en orden descendente.


¿Qué tiene que ver la clase interna con la clasificación? ¿Y qué tiene que ver la clasificación con la pregunta?
nawfal
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.