"Punteros a métodos" en Java

Una de las cosas que extrañaba de C en Java es la capacidad de apuntar a una función. En lugar de invocar a una función directamente, podía hacerlo a través de una variable. Esto es: no importa a qué apunta a esa variable, cuando se invoca con los parámetros correctos, lo ejecutará.

En Java lo más cercano que se podía hacer era usando polimorfismo. A partir de Java 8 apareció lo que veremos en este post.



Consideremos este pequeño código en C.

#include <stdio.h>
#include <stdlib.h>
/**
* Un método que imprime el parámetro
* @param a
*/
void funcion01(int a) {
printf("el valor de 'a' es %d\n", a);
}
/**
* Otro método que imprime el cuadrado del parámetro
* @param a
*/
void funcion02(int a) {
printf("el cuadrado de 'a' es %d\n", a * a);
}
/*
* función principal
*/
int main(int argc, char** argv) {
void (*p)(int) = &funcion01; //apunto al espacio de la memoria de funcion01
(*p)(10); //invoco a esa porción
p = &funcion02; //apunto a la memoria de funcion02
(*p)(10); //la misma invocación, pero ejecuta otro espacio de memoria
return (EXIT_SUCCESS);
}

Al ejecutarse esta es la salida:

La misma invocación, pero ejecuciones diferentes.

Si lo pensamos en Java, lo más parecido sería tener un método abstract (como de interface o de una clase abstracta) y dependiendo de su instanciación podremos tener el resultado diferente:

package com.apuntesdejava.methods.param.example;
public class EjemploPolimorfismo01 {
public static void main(String[] args) {
I1 a = new A();
run(a); //ejecuta el método
a = new B();
run(a); //misma invocación pero ejecuta otra instancia
}
static void run(I1 i) {
i.metodo01(10); //ejecuta el método instanciado
}
}
interface I1 {
void metodo01(int a);
}
class A implements I1 {
@Override
public void metodo01(int a) {
System.out.println("El valor de 'a' es " + a);
}
}
class B implements I1 {
@Override
public void metodo01(int a) {
System.out.println("El cuadrado de 'a' es " + (a * a));
}
}


(Sí, mucho código para hacer lo mismo). La ejecución muestra el mismo resultado.

Ahora bien, se puede hasta reducir un poco más el código evitando crear las clases, solo implementamos la interfaz directamente:


package com.apuntesdejava.methods.param.example;
public class EjemploPolimorfismo02 {
public static void main(String[] args) {
run((int a) -> {
System.out.println("El valor de 'a' es " + a);
}); //ejecuta el método
run((int a) -> {
System.out.println("El cuadrado de 'a' es " + (a * a));
}); //misma invocación pero ejecuta otra instancia
}
static void run(I2 i) {
i.metodo01(10); //ejecuta el método instanciado
}
}
interface I2 {
void metodo01(int a);
}


Ya, tenemos menos código... pero aún estamos dependiendo de una interfaz. La cuestión es que, si quiero ejecutar un método que lo definí en alguna clase y no necesito crear una interfaz para ejecutar dependiendo de la implementación... ¿cómo lo hago?

Dos clases diferentes, dos métodos diferentes

Supongamos que tengamos dos clases, que en lo único que coinciden es un método que tienen la misma cantidad de parámetros. No tienen los mismos nombres, porque el diseño de mi aplicación funciona así (así que implementar una interfaz queda fuera). Estas clases son:

package com.apuntesdejava.methods.param.example;
public class NumberPrint {
public void print(int number) {
System.out.printf("El valor del parámetro es %d\n", number);
}
}


package com.apuntesdejava.methods.param.example;
public class NumberSquared {
public void showNumber(int a) {
System.out.println("El cuadrado del parámetro es: " + (a * a));
}
}

Ahora, vamos a instanciar esas dos clases, serán dos objetos diferentes. Pero queremos, por alguna razón de nuestra lógica, invocar a esos métodos, pero el que lo invoca no sabrá a quién está ejecutando.

Para poder implementar, debemos utilizar la interfaz Consumer, y le decimos cuál es el parámetro que va a tener:

15
16
17
static void run(Consumer<Integer> c) {
    c.accept(10); //invoca el método, con el mismo parámetro
}


Y, para ejecutarlo, debemos pasarle la variable seguido de un par de dos puntos :: y seguido el nombre del método:
package com.apuntesdejava.methods.param.example;
import java.util.function.Consumer;
public class ConsumerSample {
public static void main(String[] args) {
NumberPrint np = new NumberPrint();
run(np::print); //un método de un objeto
NumberSquared ns = new NumberSquared();
run(ns::showNumber); //otro método, otro objeto
}
static void run(Consumer<Integer> c) {
c.accept(10); //invoca el método, con el mismo parámetro
}
}


Quiero obtener un valor

Hasta aquí fue un ejemplo invocando un método con un parámetro... pero ahora solo quiero invocar un método que me devuelva un valor. Vamos, tenemos estás clases:
package com.apuntesdejava.methods.param.example;
public class StringHello {
public String getHello() {
return "Hola";
}
}
package com.apuntesdejava.methods.param.example;
public class StringUpperHello {
public String hiUpper() {
return "HOLA A TODOS";
}
}


Ahora bien, para ejecutar el método podría ser así:

15
16
17
18
19
    static String run(Supplier<string> m) {
        return m.get(); //ejecuta el método cuando se le pasa
    }
 
</string>


Ahora bien, ejecutemos el código, le pasamos el método de la misma manera.
package com.apuntesdejava.methods.param.example;
import java.util.function.Supplier;
public class SupplierSample {
public static void main(String[] args) {
StringHello s1 = new StringHello();
System.out.println(run(s1::getHello)); //tiene un resultaado
StringUpperHello s2 = new StringUpperHello();
System.out.println(run(s2::hiUpper)); //tiene otro resultado, usando el mismo método
}
static String run(Supplier<String> m) {
return m.get(); //ejecuta el método cuando se le pasa
}
}

Pasar parámetro, obtener resultado

Ahora se pone interesante: pasarle un parámetro, y obtener un valor. Para eso usaremos Function

Nuestras clases a probar:

package com.apuntesdejava.methods.param.example;
public class StringUpper {
public static String upper(String s) {
return s.toUpperCase();
}
}
package com.apuntesdejava.methods.param.example;
public class StringPalindrome {
public static String go(String s) {
StringBuilder r = new StringBuilder();
for (char c : s.toCharArray()) {
r.insert(0, c);
}
return r.toString();
}
}


Ahora, ejecutaremos para ejecutar será:

19
20
21
static String run(Function<String, String> f, String p) {
    return f.apply(p); //ejecuta la funcion con el parametro y lo devuelve
}


y la ejecución será así:
package com.apuntesdejava.methods.param.example;
import java.util.function.Function;
public class FunctionSample {
public static void main(String[] args) {
String r1 = run(StringUpper::upper, "Hola a todos");
System.out.println(r1);
String r2 = run(StringPalindrome::go, "Anitalavalatina");
System.out.println(r2);
}
static String run(Function<String, String> f, String p) {
return f.apply(p); //ejecuta la funcion con el parametro y lo devuelve
}
}

Documentación

Para más detalle, revisar la documentación Package java.util.function

Comentarios

Entradas más populares de este blog

Portales en Java

Cambiar ícono a un JFrame

UML en NetBeans