Los métodos referenciados son una de las nuevas características de Java 8 que nos permite hacer referencia a los métodos y constructores por medio de una interface funcional, dicho de otra manera, podemos implementar la funcionalidad de un método abstracto por medio de la implementación de un método ya implementado, asignando el método implementado al método abstracto. Esto puede resultar un tanto extraño, sobre todo porque Java era, hasta la versión 8 un lenguaje demasiado estricto.
La siguiente imagen muestra más claro cómo funciona la referencia a métodos:
En la imagen podemos ver una interface funcional, la cual define un método abstracto, por otro lado, tenemos una clase Custom, la cual define un método implementado, este método tiene un cuerpo y una funcionalidad previamente implementada. Sabemos que el método abstracto de la interface funcional no tiene implementada una funcionalidad, pues es una interface, y queremos que el método abstracto haga referencia al método de la clase Custom, por lo cual “asignamos” o referenciamos el método de la clase custom por medio del método de la interface funcional. Para que un método pueda ser referenciado debe de coincidir los parámetros y el retorno de la interface funcional con el del método que queremos referenciar, por lo tanto, el nombre del método no importaría.
¿Pero cómo funciona esto?
Cuando referenciamos un método, lo que pasa es que la funcionalidad del método original es implementada con una referencia al método referenciado, esto quieres decir que cuando ejecutemos el método de la interface funcional, en realidad lo que pasara es que se ejecutara el método de la otra clase.
Tipos de métodos referenciados:
• Referencia a un método estático
• Referencia a un método de un objeto
• Referencia a un método de un objeto arbitrario
• Referencia a un constructor
Antes de explicarlos, me gustaría que vieras el código, trates de familiarizarte con las clases para después entrar en la explicación. El código completo lo puedes encontrar GitHub: https://github.com/oscarjb1/MethodReferences.git
La siguiente clase es un Interface Funcional debidamente anotada con @FunctionalInterface , Esta interface la cual utilizaremos como base para referenciar los métodos.
package com.osb.referencedmethod; /** * @author Oscar Blancarte <oscarblancarte3@gmail.com> */ @FunctionalInterface public interface IHello { public void sayHello(); }
La siguiente clase será utilizada para referencias a su constructor.
package com.osb.referencedmethod; /** * @author Oscar Blancarte <oscarblancarte3@gmail.com> */ public class Hello implements IHello { private String helloMessage; public Hello() { System.out.println("Hey!!! i'am a constructor"); } public void createHello(String helloMessage) { this.helloMessage = helloMessage; } @Override public void sayHello() { System.out.println(this.helloMessage); } }
La siguiente clase tiene el contenido más importante de este artículo, en ella veremos los 4 tipos de referencias a métodos.
package com.osb.referencedmethod; import com.osb.referencedmethod.IHello; import java.util.Arrays; /** * @author Oscar Blancarte <oscarblancarte3@gmail.com> */ public class Methods { public static void sayStaticHello() { System.out.println("Hey!!! i'am a static hello!"); } public void sayInstanceHello() { System.out.println("Hey!!! i'am a instance hello"); } public static void main(String[] args) { //Referencia a un método estatico IHello staticRef = Methods::sayStaticHello; staticRef.sayHello(); //Referencia a un método de un objeto Methods methods = new Methods(); IHello instanceRef = methods::sayInstanceHello; instanceRef.sayHello(); //Referencia a un método de un objeto arbitrario String[] names = new String[]{"Oscar", "Alex", "Maria", "Samuel", "Perla", "Fausto"}; Arrays.sort(names, String::compareToIgnoreCase); System.out.println("Hey!!! i'am a ordered array " + Arrays.toString(names)); //Referencia a un constructor IHello hello = Hello::new; hello.sayHello(); } }
Una vez, analizadas las clases pasaremos explicar, pero antes, me gustaría que vieras el resultado de la ejecución del proyecto:
Hey!!! i'am a static hello! Hey!!! i'am a instance hello Hey!!! i'am a ordered array [Alex, Fausto, Maria, Oscar, Perla, Samuel] Hey!!! i'am a constructor
¡Impresionante no!!!, ¿esperabas este resultado? Si es así, entonces quieres decir que ya estas más familiarizado con Java 8 y las expresiones lambda, pero si no es así, no importa, porque a continuación explicaremos lo que está pasando. Para hacer esta explicación más simple, asumiremos que ya conocemos que son las interfaces funcionales, si no es así, aquí explico cómo. Pues bien, iniciaremos con las referencias a los métodos estáticos, para lo cual regresaremos a las líneas 20-21, observemos que estamos creando la variable staticRef, la cual es de tipo IHello (Recordemos que IHello es una interface funcional), pero al asignarle valor a la variable, no lo hacemos con un new, si no que indicamos que la interface funcional se debe implementar con el método sayStaticHello el cual está definido en la clase Methods, para hacer la referencia al método sayStaticHello de la clase Methods. Para hacer la referencia se utiliza :: (Cuadro puntos o dos dobles punto). Al ejecutar el método en la línea 21, vemos que se imprime “Hey!!! i’am a static hello!”.
La otra forma de implementar las referencias es por medio de los métodos a un objeto en particular como en las líneas 24-26, en la cual vemos que primero instanciamos la clase Methods, luego volvemos a utilizar :: para hacer referencia al método sayInstanceHello, observa que esta vez utilizamos el objeto en lugar de la clase, ya que este método no es static. Al ejecutar el método se imprimirá “Hey!!! i’am a instance hello”.
Otra de las formas de implementar la referencia a métodos es por medio de un objeto arbitrario. Se dice arbitrario, porque en realidad no sabes sobre que objeto vamos hacer la referencia, pero si sabes el tipo de datos, por lo tanto, podemos asumir los métodos que tiene para referenciarlos. Un ejemplo de esto está en las líneas 29-31. Veamos lo que vamos a hacer, tenemos un Array de Strings el cual tiene una serie de nombre que queremos ordenar, para lo cual vamos a utilizar la clase Arrays, esta clase nos permite ordenar Objetos que implementen la interface Comparator, esta interface es una interface funcional debido a que solo tiene el método public int compareTo(T o), por lo que podríamos mandar una referencia a un método que pueda implementarla, en este caso veamos que al método sort le mandamos el array de nombre y una referencia al método compareToIgnoreCase el cual tiene la siguiente definición public int compareToIgnoreCase(String str), veamos que tanto compareTo y compareToIgnoreCase reciben el String a comprar y los dos regresan un int, por lo tanto la referencia a métodos es compatible. Lo que pasa entonces en la línea 30, es que la lista será iterada, y por cada String, llamará a su método compareToIgnoreCase, notemos algo muy importante…. La instrucción String::compareToIgnoreCase, no es una referencia a un método estático como podríamos creer, en su lugar, como sabemos que es una lista de String, le estamos diciendo que de cada String que está en la lista, use su método compareToIgnoreCase para comprar el siguiente elemento de la lista. Es por eso que se llama referencia a métodos de objetos arbitrario, porque conocemos el tipo de datos, pero no el objeto concreto. Al imprimir la lista, veremos una lista correctamente ordenada: “Hey!!! i’am a ordered array [Alex, Fausto, Maria, Oscar, Perla, Samuel]”
Finalmente, tenemos la referencia a constructores, la cual nos permitirá asignar la referencia de un constructor a el método de la interface funcional, de tal forma que cuando ejecutemos el método, este en realidad llamará al constructor de la clase referenciada. Como vemos en las líneas 34-35, estamos haciendo referencia al constructor mediante los :: (cuatro puntos) seguido del operador new, así de fácil. Finalmente cuando ejecutemos el método sayHello, este en realidad llamará al constructor de la clase Hello y se imprimirá en pantalla: “Hey!!! i’am a constructor”. Recordemos que las clases pueden tener más de un constructor, ¿entonces como sabe Java a que constructor llamar?, pues es fácil, llamará al que tenga compatibilidad con los parámetros de la interface funcional. Como en este caso, el método sayHello, no recibe parámetros, entonces se llama al constructor sin parámetros de la clase Hello. Por otra parte, si el método recibiera un String, entonces llamaría al constructor que recibe un solo parámetro. ¡Así de fácil!
Conclusiones:
Desde mi punto de vista, los métodos y constructores referenciados fueron de las cosas más extrañas que me parecieron del Java 8, pues al inicio no lograba entender cómo funcionaba a la perfección, sobre todo la referencia a métodos de un objeto arbitrario, pero la verdad es que es solo práctica, tenemos que descargar el código y probar, modificar las clases y probar de nuevo. Si no experimentamos se nos hará muy difícil entenderlas, pero más a un recordarlas cuando las necesitemos.
Como verás esta fue una guía rápida de los métodos referenciados, pero si tienes dudas, déjamelas en los comentarios y con gusto las resolveré. Recuerda que puedes descargar el código y a practicar.
Si estas interesado en seguir aprendiendo más de las mejoras de Java 8 te dejo este Enlace donde están todos los artículos que he escrito sobre Java 8