1. Métodos de función

Si una interfaz tiene solo un método , a una variable de ese tipo de interfaz se le puede asignar un valor dado por una expresión lambda (función lambda). Tales interfaces se conocieron como interfaces funcionales (después de que Java agregó soporte para funciones lambda).

Por ejemplo, Java tiene la Consumer<Type>interfaz, que tiene el accept(Type obj)método. ¿Por qué es necesaria esta interfaz?

En Java 8, las colecciones tienen un forEach()método que le permite realizar alguna acción para cada elemento de la colección . Y aquí Consumer<T>se usa la interfaz funcional para pasar la acción al forEach()método.

Así es como puede mostrar todos los elementos de una colección :

ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "Hello", "how's", "life?");

list.forEach( (s) -> System.out.println(s) );
Mostrar todos los elementos de una colección (usando una expresión lambda)

El compilador convertirá el código anterior al código siguiente:

ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "Hello", "how's", "life?");

list.forEach(new Consumer<String>()
{
   public void accept(String s)
   {
      System.out.println(s));
   }
});
Mostrar todos los elementos de una colección (usando una clase anónima)

La primera versión es definitivamente más corta que la segunda. Y mientras que el código con expresiones lambda es difícil de leer, el código con clases internas anónimas a veces es aún más difícil de leer.



2. Referencia del método

Sin embargo, nuestro código de expresión lambda se puede escribir incluso más corto.

Primero, puede omitir los paréntesis alrededor del sparámetro:

list.forEach( (s) -> System.out.println(s) );
Antes
list.forEach( s -> System.out.println(s) );
Después

Esto solo se puede hacer si hay un parámetro . Si hay varios parámetros, debe usar paréntesis .

Y segundo, puedes escribirlo así:

list.forEach( System.out::println );
Notación más compacta

Esta es exactamente la misma notación. Tenga en cuenta que no hay paréntesis después de println.

Aquí tenemos el mismo código: una llamada de método:

object::method
x -> object.method(x)

Piénsalo: queríamos realizar alguna acción para cada elemento de la listcolección. Si la acción es una sola llamada de función (como println()), entonces tiene sentido simplemente pasar la función al método como un parámetro.

Pero, ¿cómo le explicamos al compilador que queremos pasar el método en lugar de llamarlo? Para hacer esto, en lugar del operador punto, usamos dos puntos antes del nombre del método. Ya se usa un solo dos puntos para indicar el operador ternario.

Esta es la notación más simple y compacta.



3. Constructor

Las referencias a métodos con dos puntos dobles son muy útiles cuando trabajamos con flujos de E/S. Verás esto un poco más tarde.

Mientras tanto, hablemos de 3 formas populares de pasar una referencia de método:

Referencia a un método de un objeto

Para pasar una referencia a un método de un objeto, debe escribir algo como . Este código es equivalente a .object::method
x -> object.method(x)

El especial thisy superlas variables se pueden utilizar como objeto.

Referencia a un método de una clase

Para pasar una referencia a un método estático, debe escribir algo como . Este código se convierte en código comoclass::methodx -> class.method(x);

Referencia a un constructor

Un constructor se comporta de manera similar a un método de clase estático, por lo que también puede pasar una referencia a un constructor. Así es como se ve: .class::new

Por ejemplo, puede sortear el borrado de tipos para colecciones y pasar al toArray()método una referencia a un constructor que creará la matriz deseada:toArray(int[]::new);