Empiece a utilizar referencias de métodos en Java

Junto con lambdas, Java SE 8 trajo referencias de métodos al lenguaje Java. Este tutorial ofrece una breve descripción general de las referencias a métodos en Java y, a continuación, le permite comenzar a usarlos con ejemplos de código Java. Al final del tutorial, sabrá cómo usar las referencias de métodos para hacer referencia a los métodos estáticos de una clase, los métodos no estáticos vinculados y no vinculados y los constructores, así como cómo usarlos para hacer referencia a métodos de instancia en la superclase y la clase actual. tipos. También comprenderá por qué muchos desarrolladores de Java han adoptado expresiones lambda y referencias de métodos como una alternativa más limpia y sencilla a las clases anónimas.

Tenga en cuenta que los ejemplos de código de este tutorial son compatibles con JDK 12.

descargar Obtener el código Descargar el código fuente, por ejemplo, las aplicaciones de este tutorial. Creado por Jeff Friesen para JavaWorld.

Referencias de métodos: una cartilla

Mi tutorial anterior de Java 101 introdujo expresiones lambda, que se utilizan para definir métodos anónimos que luego pueden tratarse como instancias de una interfaz funcional. A veces, una expresión lambda no hace más que llamar a un método existente. Por ejemplo, el siguiente fragmento de código usa una lambda para invocar System.outel void println(s)método en el único argumento de la lambda s; el tipo aún no se conoce:

(s) -> System.out.println(s)

La lambda se presenta (s)como su lista de parámetros formales y un cuerpo de código cuya System.out.println(s)expresión imprime sel valor en el flujo de salida estándar. No tiene un tipo de interfaz explícito. En cambio, el compilador infiere del contexto circundante qué interfaz funcional instanciará. Por ejemplo, considere el siguiente fragmento de código:

Consumer consumer = (s) -> System.out.println(s);

El compilador analiza la declaración anterior y determina que el método de java.util.function.Consumerla interfaz funcional predefinida void accept(T t)coincide con la lista de parámetros formales de lambda ( (s)). También determina que accept()el voidtipo de retorno coincide con println()el voidtipo de retorno. Por tanto, la lambda está vinculada a Consumer.

Más específicamente, la lambda está vinculada a Consumer. El compilador genera código para que una invocación de Consumer's void accept(String s)método da como resultado el argumento de cadena que se pasa a sque se pasa a System.out' s void println(String s)método. Esta invocación se muestra a continuación:

consumer.accept("Hello"); // Pass "Hello" to lambda body. Print Hello to standard output.

Para ahorrar pulsaciones de teclas, puede reemplazar el lambda con una referencia de método , que es una referencia compacta a un método existente. Por ejemplo, el siguiente sustituye fragmento de código (String s) -> System.out.println(s)con System.out::println, donde ::significa que System.out's void println(String s)está siendo referenciado método:

Consumer consumer2 = System.out::println; // The method reference is shorter. consumer2.accept("Hello"); // Pass "Hello" to lambda body. Print Hello to standard output.

No es necesario especificar una lista de parámetros formales para la referencia del método anterior porque el compilador puede inferir esta lista basándose en Consumerel java.lang.Stringargumento de tipo real de este tipo parametrizado reemplaza Ten void accept(T t), y también es el tipo de parámetro único en la System.out.println()llamada al método del cuerpo lambda .

Referencias de métodos en profundidad

Una referencia de método es un atajo sintáctico para crear una lambda a partir de un método existente. En lugar de proporcionar un cuerpo de implementación, una referencia de método se refiere al método de una clase u objeto existente. Al igual que con una lambda, una referencia de método requiere un tipo de destino.

Puede utilizar referencias de método para hacer referencia a los métodos estáticos de una clase, los métodos no estáticos vinculados y no vinculados y los constructores. También puede utilizar referencias a métodos para hacer referencia a métodos de instancia en superclase y tipos de clases actuales. Le presentaré cada una de estas categorías de referencia de métodos y mostraré cómo se usan en una pequeña demostración.

Obtenga más información sobre las referencias de métodos

Después de leer esta sección, consulte Referencias de métodos en Java 8 (Toby Weston, febrero de 2014) para obtener más información sobre las referencias de métodos en contextos de métodos no estáticos vinculados y no vinculados.

Referencias a métodos estáticos

Una referencia a un método estático se refiere a un método estático en una clase específica. Su sintaxis es , donde identifica la clase e identifica el método estático. Un ejemplo es . El Listado 1 muestra una referencia de método estático.className::staticMethodNameclassNamestaticMethodNameInteger::bitCount

Listado 1. MRDemo.java (versión 1)

import java.util.Arrays; import java.util.function.Consumer; public class MRDemo { public static void main(String[] args) { int[] array = { 10, 2, 19, 5, 17 }; Consumer consumer = Arrays::sort; consumer.accept(array); for (int i = 0; i < array.length; i++) System.out.println(array[i]); System.out.println(); int[] array2 = { 19, 5, 14, 3, 21, 4 }; Consumer consumer2 = (a) -> Arrays.sort(a); consumer2.accept(array2); for (int i = 0; i < array2.length; i++) System.out.println(array2[i]); } }

El main()método del Listado 1 ordena un par de matrices de enteros a través java.util.Arraysdel static void sort(int[] a)método de la clase , que aparece en la referencia de método estático y en contextos de expresión lambda equivalentes. Después de ordenar una matriz, un forbucle imprime el contenido de la matriz ordenada en el flujo de salida estándar.

Antes de que podamos usar una referencia de método o una lambda, debe estar vinculada a una interfaz funcional. Estoy usando la Consumerinterfaz funcional predefinida , que cumple con los requisitos de referencia del método / lambda. La operación de clasificación comienza pasando la matriz a la que se ordenará Consumerel accept()método.

Compile el Listado 1 ( javac MRDemo.java) y ejecute la aplicación ( java MRDemo). Observará el siguiente resultado:

2 5 10 17 19 3 4 5 14 19 21

Referencias a métodos no estáticos enlazados

Una referencia de método no estático vinculado se refiere a un método no estático que está vinculado a un objeto receptor . Su sintaxis es , donde identifica al receptor e identifica el método de instancia. Un ejemplo es . El Listado 2 muestra una referencia de método no estático enlazado.objectName::instanceMethodNameobjectNameinstanceMethodNames::trim

Listado 2. MRDemo.java (versión 2)

import java.util.function.Supplier; public class MRDemo { public static void main(String[] args) { String s = "The quick brown fox jumped over the lazy dog"; print(s::length); print(() -> s.length()); print(new Supplier() { @Override public Integer get() { return s.length(); // closes over s } }); } public static void print(Supplier supplier) { System.out.println(supplier.get()); } }

El main()método del Listado 2 asigna una cadena a la Stringvariable sy luego invoca el print()método de clase con funcionalidad para obtener la longitud de esta cadena como argumento de este método. print()se invoca en la referencia de método ( s::length- length()está vinculado a s), lambda equivalente y contextos de clase anónimos equivalentes.

He definido print()usar la java.util.function.Supplierinterfaz funcional predefinida, cuyo get()método devuelve un proveedor de resultados. En este caso, la Supplierinstancia pasada a print()implementa su get()método para regresar s.length(); print()produce esta longitud.

s::length introduces a closure that closes over s. You can see this more clearly in the lambda example. Because the lambda has no arguments, the value of s is only available from the enclosing scope. Therefore, the lambda body is a closure that closes over s. The anonymous class example makes this even clearer.

Compile Listing 2 and run the application. You'll observe the following output:

44 44 44

References to unbound non-static methods

An unbound non-static method reference refers to a non-static method that's not bound to a receiver object. Its syntax is className::instanceMethodName, where className identifies the class that declares the instance method and instanceMethodName identifies the instance method. An example is String::toLowerCase.

String::toLowerCase is an unbound non-static method reference that identifies the non-static String toLowerCase() method of the String class. However, because a non-static method still requires a receiver object (in this example a String object, which is used to invoke toLowerCase() via the method reference), the receiver object is created by the virtual machine. toLowerCase() will be invoked on this object. String::toLowerCase specifies a method that takes a single String argument, which is the receiver object, and returns a String result. String::toLowerCase() is equivalent to lambda (String s) -> { return s.toLowerCase(); }.

Listing 3 demonstrates this unbound non-static method reference.

Listing 3. MRDemo.java (version 3)

import java.util.function.Function; public class MRDemo { public static void main(String[] args) { print(String::toLowerCase, "STRING TO LOWERCASE"); print(s -> s.toLowerCase(), "STRING TO LOWERCASE"); print(new Function() { @Override public String apply(String s) // receives argument in parameter s; { // doesn't need to close over s return s.toLowerCase(); } }, "STRING TO LOWERCASE"); } public static void print(Function function, String s) { System.out.println(function.apply(s)); } }

Listing 3's main() method invokes the print() class method with functionality to convert a string to lowercase and the string to be converted as the method's arguments. print() is invoked in method reference (String::toLowerCase, where toLowerCase() isn't bound to a user-specified object) and equivalent lambda and anonymous class contexts.

I've defined print() to use the java.util.function.Function predefined functional interface, which represents a function that accepts one argument and produces a result. In this case, the Function instance passed to print() implements its R apply(T t) method to return s.toLowerCase(); print() outputs this string.

Although the String part of String::toLowerCase makes it look like a class is being referenced, only an instance of this class is referenced. The anonymous class example makes this more obvious. Note that in the anonymous class example the lambda receives an argument; it doesn't close over parameter s (i.e., it's not a closure).

Compile Listing 3 and run the application. You'll observe the following output:

string to lowercase string to lowercase string to lowercase

References to constructors

You can use a method reference to refer to a constructor without instantiating the named class. This kind of method reference is known as a constructor reference. Its syntax is className::new. className must support object creation; it cannot name an abstract class or interface. Keyword new names the referenced constructor. Here are some examples:

  • Character::new: equivalent to lambda (Character ch) -> new Character(ch)
  • Long::new: equivalent to lambda (long value) -> new Long(value) or (String s) -> new Long(s)
  • ArrayList::new: equivalent to lambda () -> new ArrayList()
  • float[]::new: equivalent to lambda (int size) -> new float[size]

The last constructor reference example specifies an array type instead of a class type, but the principle is the same. The example demonstrates an array constructor reference to the "constructor" of an array type.

To create a constructor reference, specify new without a constructor. When a class such as java.lang.Long declares multiple constructors, the compiler compares the functional interface's type against all of the constructors and chooses the best match. Listing 4 demonstrates a constructor reference.

Listing 4. MRDemo.java (version 4)

import java.util.function.Supplier; public class MRDemo { public static void main(String[] args) { Supplier supplier = MRDemo::new; System.out.println(supplier.get()); } }

Listing 4's MRDemo::new constructor reference is equivalent to lambda () -> new MRDemo(). Expression supplier.get() executes this lambda, which invokes MRDemo's default no-argument constructor and returns the MRDemo object, which is passed to System.out.println(). This method converts the object to a string, which it prints.

Now suppose you have a class with a no-argument constructor and a constructor that takes an argument, and you want to call the constructor that takes an argument. You can accomplish this task by choosing a different functional interface, such as the predefined Function interface shown in Listing 5.

Listing 5. MRDemo.java (version 5)

import java.util.function.Function; public class MRDemo { private String name; MRDemo() { name = ""; } MRDemo(String name) { this.name = name; System.out.printf("MRDemo(String name) called with %s%n", name); } public static void main(String[] args) { Function function = MRDemo::new; System.out.println(function.apply("some name")); } }

Function function = MRDemo::new;hace que el compilador busque un constructor que tome un Stringargumento, porque Functionel apply()método de requiere un solo Stringargumento (en este contexto) . La ejecución function.apply("some name")da como resultado que "some name"se pase a MRDemo(String name).