15 marzo 2011

¿Delegates en java?

Hola a todos, en este post intentaré exponer por qué java no tiene soporte del lenguaje para los delegates y como pasar un método como parámetro en java. Si queréis una excelente introducción a los delegates no dejéis de leer este post que publico Eduard Tomàs en su blog Burbujas en .NET.

Bueno, a lo que iba, esta semana, después de un período inmerso en un proyecto con tecnología .NET he vuelto a Java i Eclipse. En este desarrollo me ha surgido la necesidad de pasar un método como parámetro. Bueno pues nada, definimos un delegate y listos:

public void delegate MyDelegate();

Sorpresa: en java NO existe la palabra reservada delegate! Oooops! ¿Qué quiere decir esto? ¿No se puede pasar un método como parámetro en java? No me lo creo… pero si se puede, ¿cómo lo hacemos sin soporte del lenguaje? Veamos, vamos por partes:


1.- ¿Tiene java soporte para los delegates?


NO, java no tiene soporte explicito para los delegates, es decir, java no tiene una palabra reservada delegate pero, evidentemente, SI tiene soporte para pasar métodos como parámetros. En el white paper About Microsoft's "Delegates" de Sun (de Oracle quise decir ;-)) se exponen los motivos para no incluir los delegates como parte del lenguaje:



  • Los delegates innecesarios por qué existen otras alternativas de diseño que proporcionan una funcionalidad igual o superior: las inner classes.
  • Los delegates añaden complejidad.
  • La introducción de los delegates en el lenguaje implica una pérdida de orientación a objeto del mismo.
  • Etc…

Como conclusión vemos que Java ofrece soporte a los delegates mediante las inner classes.


En el siguiente punto veremos como pasar métodos como parámetros con un lenguaje que proporciona soporte explícito a los delegates como C# y cómo se podría hacer en un lenguaje si soporte explicito como java.


Usaremos un ejemplo para ilustrar los conceptos anteriores, supongamos que tenemos una clase Foo con dos atributos de tipo entero i la queremos dotar de un método Operate que permita a sus clientes realizar operaciones con los valores de estos dos atributos (sumar, restar, multiplicar, dividir y cualquier operación que se le ocurra al cliente) .


2.- Uso de delegate para pasar métodos como parámetro


Con un lenguaje que proporciona soporte explícito a los delegates como C# este método se podría implementar definiendo un delegate con dos parámetros enteros y que devuelve un entero:

delegate int Operate(int x, int y);

y definiendo la clase Foo así:

class Foo
{
    public int x;
    public int y;
    public int Operate(OperationDelegate myDelegate)
    {
        return myDelegate(x, y);
    }        
}

con esto si un cliente de la clase Foo quiere sumar los dos valores de x e y lo puede hacer así:

Foo f = new Foo() { x=2, y=2};
OperationDelegate suma = new OperationDelegate((x, y) => x + y);
f.Operate(suma);

y si el cliente quiere multiplicarlos:

Foo f = new Foo() { x=2, y=2};
OperationDelegate multiplica = new OperationDelegate((x, y) => x * y);
f.Operate(multiplica);

y si el cliente quiere primero sumarlos, al resultado añadirle 15 y restar 45:

Foo f = new Foo() { x=2, y=2};
OperationDelegate otherOperation= new OperationDelegate((x, y) => 15 + x + y - 45);
f.Operate(otherOperation);

Nota: he realizado la implementación de los delegates con expresiones lambda, si no usamos lambdas se haría así:

Foo f = new Foo() { x=2, y=2};
OperationDelegate suma = new OperationDelegate(SumaDelegate);
f.Operate(suma);
int SumaDelegate(int x, int y)
{
    return x + y;
}

Fácil no? Vamos ahora a ver como se implementaría el ejemplo anterior con java, que recordemos, no ofrece soporte explicito para delegates:


3.- Uso de las inner classes para pasar métodos como parámetros


Una posible forma de implementar el ejemplo anterior en java usando anonymous inner classes seria definir una interface para el método que se quiere pasar como parámetro:

interface OperationDelegate { int invoke(int x, int y);}

y definir la clase Foo como sigue:

public class Foo {
    public int x;
    public int y;
    public int Operate(OperationDelegate myDelegate) {
	return myDelegate.invoke(x, y);
    }
}

con esto si un cliente de la clase Foo quiere sumar los dos valores de x e y lo puede hacer así:

Foo f = new Foo();
f.x = 2;
f.y = 2;
OperationDelegate suma= new OperationDelegate() {
	public int invoke(int x, int y) { return Client.Suma(x, y);}
};
f.Operate(suma);
static int Suma(int x, int y)
{
	return x + y;
}

Notas:



  • Java no soporta ni expresiones lambda ni propiedades automáticas en los constructores.

  • Se ha usado una interface para declarar el “delegado”, también se podría haber hecho mediante una clase.
  • En la definición de los métodos de una inner class solo se puede acceder a variables de tipo final.
  • Evidentemente se pueden usar inner classes para definir delegates en C#, pero parece un poco absurdo, ¿no?
  • En este artículo podéis encontrar una implementación que simula el modo de operar de los delegates de C# en java.

4.- Conclusiones


El uso de inner classes nos proporciona una funcionalidad equivalente a la que proporcionan los delegates en lenguajes que no ofrecen soporte nativo a ellos. Sin entrar a fondo en motivaciones metafísicas, creo que la inclusión de los delegates como parte de la especificación del lenguaje ofrecen una ventaja a nivel de claridad y usabilidad notable.


Saludos! Xampi

No hay comentarios: