El operador de doble colon en Java 8

1. Información general

En este artículo rápido, discutiremos el operador de dos puntos dobles ( ::) en Java 8 y repasaremos los escenarios donde se puede usar el operador.

2. De Lambdas a Operador de Doble Colon

Con las expresiones Lambdas, hemos visto que el código puede volverse muy conciso.

Por ejemplo, para crear un comparador , la siguiente sintaxis es suficiente:

Comparator c = (Computer c1, Computer c2) -> c1.getAge().compareTo(c2.getAge()); 

Luego, con inferencia de tipo:

Comparator c = (c1, c2) -> c1.getAge().compareTo(c2.getAge());

Pero, ¿podemos hacer que el código anterior sea aún más expresivo y legible? Echemos un vistazo:

Comparator c = Comparator.comparing(Computer::getAge); 

Hemos utilizado el operador :: como forma abreviada de lambdas que llaman a un método específico, por nombre. Y al final, el resultado es, por supuesto, una sintaxis aún más legible.

3. ¿Cómo funciona?

En pocas palabras, cuando estamos usando una referencia de método, la referencia de destino se coloca antes del delimitador :: y el nombre del método se proporciona después.

Por ejemplo:

Computer::getAge;

Estamos viendo una referencia de método al método getAge definido en la clase Computer .

Entonces podemos operar con esa función:

Function getAge = Computer::getAge; Integer computerAge = getAge.apply(c1); 

Observe que estamos haciendo referencia a la función y luego aplicándola al tipo correcto de argumento.

4. Referencias de métodos

Podemos hacer un buen uso de este operador en bastantes escenarios.

4.1. Un método estático

Primero, usaremos un método de utilidad estática :

List inventory = Arrays.asList( new Computer( 2015, "white", 35), new Computer(2009, "black", 65)); inventory.forEach(ComputerUtils::repair); 

4.2. Un método de instancia de un objeto existente

A continuación, echemos un vistazo a un escenario interesante: hacer referencia a un método de una instancia de objeto existente .

Usaremos la variable System . out : un objeto de tipo PrintStream que admite el método de impresión :

Computer c1 = new Computer(2015, "white"); Computer c2 = new Computer(2009, "black"); Computer c3 = new Computer(2014, "black"); Arrays.asList(c1, c2, c3).forEach(System.out::print); 

4.3. Un método de instancia de un objeto arbitrario de un tipo particular

Computer c1 = new Computer(2015, "white", 100); Computer c2 = new MacbookPro(2009, "black", 100); List inventory = Arrays.asList(c1, c2); inventory.forEach(Computer::turnOnPc); 

Como puede ver, estamos haciendo referencia al método turnOnPc no en una instancia específica, sino en el tipo en sí.

En la línea 4, se llamará al método de instancia turnOnPc para cada objeto del inventario .

Y esto naturalmente significa que - para c1, el método turnOnPc será llamado en la instancia de Computer y para c2 en la instancia de MacbookPro .

4.4. Un súper método de un objeto en particular

Suponga que tiene el siguiente método en la superclase Computer :

public Double calculateValue(Double initialValue) { return initialValue/1.50; } 

y este en la subclase MacbookPro :

@Override public Double calculateValue(Double initialValue){ Function function = super::calculateValue; Double pcValue = function.apply(initialValue); return pcValue + (initialValue/10) ; } 

Una llamada al método calculateValue en una instancia de MacbookPro :

macbookPro.calculateValue(999.99); 

también producirá también una llamada a calculateValue en la superclase Computer .

5. Referencias de constructores

5.1. Crear una nueva instancia

Hacer referencia a un constructor para instanciar un objeto puede ser bastante simple:

@FunctionalInterface public interface InterfaceComputer { Computer create(); } InterfaceComputer c = Computer::new; Computer computer = c.create(); 

¿Qué pasa si tienes dos parámetros en un constructor?

BiFunction c4Function = Computer::new; Computer c4 = c4Function.apply(2013, "white"); 

Si los parámetros son tres o más, debe definir una nueva interfaz funcional:

@FunctionalInterface interface TriFunction { R apply(A a, B b, C c); default  TriFunction andThen( Function after) { Objects.requireNonNull(after); return (A a, B b, C c) -> after.apply(apply(a, b, c)); } } 

Luego, inicializa tu objeto:

TriFunction  c6Function = Computer::new; Computer c3 = c6Function.apply(2008, "black", 90); 

5.2. Crear una matriz

Finalmente, veamos cómo crear una matriz de objetos Computer con cinco elementos:

Function  computerCreator = Computer[]::new; Computer[] computerArray = computerCreator.apply(5); 

6. Conclusión

Como estamos empezando a ver, el operador de dos puntos, introducido en Java 8, será muy útil en algunos escenarios, y especialmente en conjunto con Streams.

También es muy importante echar un vistazo a las interfaces funcionales para comprender mejor lo que sucede detrás de escena.

El código fuente completo para el ejemplo está disponible en este proyecto de GitHub; este es un proyecto de Maven y Eclipse para que se pueda importar y usar tal cual.