Breaking

Post Top Ad

Your Ad Spot

viernes, 14 de junio de 2019

Expresiones Lambda en Java

Introducción

Las funciones Lambda han sido una adición que viene con Java 8 , y fue el primer paso del lenguaje hacia la programación funcional , siguiendo una tendencia general hacia la implementación de características útiles de varios paradigmas compatibles .
La motivación para introducir funciones lambda fue principalmente para reducir el incómodo código repetitivo que se usaba para transmitir instancias de clase para simular funciones anónimas de otros idiomas.
Aquí hay un ejemplo:
String[] arr = { "family", "illegibly", "acquired", "know", "perplexing", "do", "not", "doctors", "where", "handwriting", "I" };

Arrays.sort(arr, new Comparator<String>() {  
    @Override public int compare(String s1, String s2) {
        return s1.length() - s2.length();
    }
});

System.out.println(Arrays.toString(arr));  
Como puede ver, todo el proceso de creación de instancias de una nueva clase de Comparator y la anulación de su contenido es un fragmento de código repetitivo del que podemos prescindir, ya que siempre es lo mismo.
La Arrays.sort()línea completa se puede reemplazar por algo mucho más corto y más dulce, pero funcionalmente equivalente:
Arrays.sort(arr, (s1,s2) -> s1.length() - s2.length());  
Estos bits cortos y dulces de código que hacen lo mismo que sus contrapartes verbales se llaman azúcar sintáctica . Esto se debe a que no agregan funcionalidad a un idioma, sino que lo hacen más compacto y legible. Las funciones Lambda son un ejemplo de azúcar sintáctica para Java.
Aunque sugiero encarecidamente leer este artículo en orden, si no está familiarizado con el tema, aquí hay una lista rápida de lo que cubriremos para una referencia más fácil:
  • Lambdas como objetos
    • Coincidencia de interfaz de un solo método
  • Implementación
    • Parámetros
    • Cuerpo
    • Captura variable
    • Método de referencia
      • Método estático de referencia
      • Parámetro Método de referencia
      • Método de referencia de instancia
      • Método de referencia del constructor

Lambdas como objetos

Antes de entrar en el meollo de la cuestión de la sintaxis lambda sí, debemos echar un vistazo a lolambda funciones están en el primer lugar y cómo se utilizan .
Como se mencionó, son simplemente azúcar sintáctica, pero son azúcar sintáctica específicamente para objetos que implementan una única interfaz de método.
En esos objetos, la implementación lambda se considera la implementación de dicho método. Si la lambda y la interfaz coinciden, la función lambda se puede asignar a una variable del tipo de esa interfaz.

Coincidencia de interfaz de un solo método

Con el fin de hacer coincidir un lambda con una sola interfaz de método, también llamada "interfaz funcional", se deben cumplir varias condiciones:
  • La interfaz funcional debe tener exactamente un método no implementado, y ese método (naturalmente) debe ser abstracto. La interfaz puede contener métodos estáticos y predeterminados implementados dentro de ella, pero lo importante es que hay exactamente un método abstracto.
  • El método abstracto tiene que aceptar argumentos, en el mismo orden, que corresponden a los parámetros que acepta lambda.
  • El tipo de retorno tanto del método como de la función lambda tiene que coincidir.
Si todo esto se cumple, se han cumplido todas las condiciones para la comparación y puede asignar su lambda a la variable.
Vamos a definir nuestra interfaz:
public interface HelloWorld {  
    abstract void world();
}
Como puedes ver, tenemos una interfaz funcional bastante inútil.
Contiene exactamente una función, y esa función puede hacer cualquier cosa, siempre que no acepte argumentos y no devuelva valores.
Vamos a hacer un simple programa Hello World usando esto, aunque la imaginación es el límite si quieres jugar con él:
public class Main {  
    public static void main(String[] args) {
        HelloWorld hello = () -> System.out.println("Hello World!");
        hello.world();
    }
}
Como podemos ver si ejecutamos esto, nuestra función lambda ha coincidido exitosamente con la HelloWorldinterfaz, y el objeto helloahora puede usarse para acceder a su método.
La idea detrás de esto es que puede usar lambdas donde quiera que usaría interfaces funcionales para transmitir funciones. Si recuerda nuestro Comparatorejemplo, Comparator<T>es en realidad una interfaz funcional, la implementación de un método único - compare().
Es por eso que podríamos reemplazarlo con un lambda que se comporte de manera similar a ese método.

Implementación

La idea básica detrás de las funciones lambda es la misma que la idea básica detrás de los métodos: toman parámetros y los usan dentro del cuerpo que consiste en expresiones.
La implementación es un poco diferente. Tomemos el ejemplo de nuestra Stringclasificación lambda:
(s1,s2) -> s1.length() - s2.length()
Su sintaxis se puede entender como:
parameters -> body  

Parámetros

Los parámetros son los mismos que los parámetros de función, son valores que se pasan a una función lambda para que haga algo.
Los parámetros suelen estar entre paréntesis y separados por comas, aunque en el caso de un lambda, que recibe solo un parámetro, se pueden omitir los paréntesis.
Una función lambda puede tomar cualquier número de parámetros, incluido el cero, por lo que podría tener algo como esto:
() -> System.out.println("Hello World!")
Esta función lambda, cuando coincida con una interfaz correspondiente, funcionará igual que la función siguiente:
static void printing(){  
    System.out.println("Hello World!");
}
De manera similar, podemos tener funciones lambda con uno, dos o más parámetros.
Un ejemplo clásico de una función con un parámetro está trabajando en cada elemento de una colección en un forEachbucle:
public class Main {  
    public static void main(String[] args) {
        LinkedList<Integer> childrenAges = new LinkedList<Integer>(Arrays.asList(2, 4, 5, 7));
        childrenAges.forEach( age -> System.out.println("One of the children is " + age + " years old."));
    }
}
Aquí, el único parámetro es ageTenga en cuenta que aquí eliminamos los paréntesis, porque eso está permitido cuando solo tenemos un parámetro.
El uso de más parámetros funciona de manera similar, solo están separados por una coma y entre paréntesis. Ya hemos visto lambda de dos parámetros cuando lo combinamos Comparatorpara ordenar las cadenas.

Cuerpo

Un cuerpo de una expresión lambda consiste en una sola expresión o un bloque de declaración.
Si especifica solo una expresión como el cuerpo de una función lambda (ya sea en un bloque de instrucciones o por sí mismo), lambda devolverá automáticamente la evaluación de esa expresión.
Si tiene varias líneas en su bloque de estado de cuenta, o si solo quiere (es un país libre), puede usar explícitamente una declaración de retorno dentro de un bloque de estado de cuenta:
// just the expression
(s1,s2) -> s1.length() - s2.length()

// statement block
(s1,s2) -> { s1.length() - s2.length(); }

// using return
(s1,s2) -> {
    s1.length() - s2.length();
    return; // because forEach expects void return
}
Puede intentar sustituir cualquiera de estos en nuestro ejemplo de clasificación al principio del artículo, y encontrará que todos funcionan exactamente igual.

Captura variable

La captura de variables permite que las lambdas utilicen variables declaradas fuera de la propia lambda.
Hay tres tipos muy similares de captura de variables:
  • captura de variable local
  • captura de variable de instancia
  • captura de variable estática
La sintaxis es casi idéntica a la forma en que accedería a estas variables desde cualquier otra función, pero las condiciones en las que puede hacerlo son diferentes.
Puede acceder a una variable local solo si es efectivamente final , lo que significa que no cambia su valor después de la asignación. No tiene que ser declarado explícitamente como definitivo, pero es recomendable hacerlo para evitar confusiones. Si lo usa en una función lambda y luego cambia su valor, el compilador comenzará a gimotear.
La razón por la que no puede hacer esto es porque la lambda no puede hacer referencia de manera confiable a una variable local, ya que puede destruirse antes de ejecutar la lambda. Debido a esto, hace una copia profunda . Cambiar la variable local puede llevar a un comportamiento confuso, ya que el programador puede esperar que el valor dentro de la lambda cambie, por lo que para evitar confusiones, está explícitamente prohibido.
Cuando se trata de variables de instancia , si su lambda está dentro de la misma clase que la variable a la que está accediendo, simplemente puede usar this.fieldpara acceder a un campo en esa clase. Además, el campo no tiene que ser definitivo , y se puede cambiar más adelante durante el curso del programa.
Esto se debe a que si se define un lambda dentro de una clase, se crea una instancia junto con esa clase y se vincula a esa instancia de clase, y por lo tanto puede referirse fácilmente al valor del campo que necesita.
Las variables estáticas se capturan de forma muy similar a las variables de instancia, excepto por el hecho de que no utilizaría thispara referirse a ellas. Se pueden cambiar y no necesitan ser definitivos por las mismas razones.

Método de referencia

A veces, las lambdas son simplemente complementos para un método específico. En el espíritu de hacer que la sintaxis sea corta y dulce, en realidad no tiene que escribir toda la sintaxis cuando ese es el caso. Por ejemplo:
s -> System.out.println(s)  
es equivalente a:
System.out::println  
La ::sintaxis le permitirá al compilador saber que solo quieres un lambda que pase por el argumento dado printlnSiempre debe comenzar con el nombre del método ::donde escribiría una función lambda, de lo contrario, acceda al método como lo haría normalmente, lo que significa que aún debe especificar la clase del propietario antes de los dos puntos.
Hay varios tipos de referencias de métodos, dependiendo del tipo de método que está llamando:
  • referencia al método estático
  • parámetro de referencia del método
  • referencia de método de instancia
  • referencia del método constructor
Método estático de referencia
Necesitamos una interfaz:
public interface Average {  
    abstract double average(double a, double b);
}
Una función estática:
public class LambdaFunctions {  
    static double averageOfTwo(double a, double b){
        return (a+b)/2;
    }
}
Y nuestra función lambda y llamada en main:
Average avg = LambdaFunctions::averageOfTwo;  
System.out.println(avg.average(20.3, 4.5));  
Parámetro Método de referencia
Una vez más, estamos escribiendo main.
Comparator<Double> cmp = Double::compareTo;  
Double a = 20.3;  
System.out.println(cmp.compare(a, 4.5));  
La Double::compareTolambda es equivalente a:
Comparator<Double> cmp = (a, b) -> a.compareTo(b)  
Método de referencia de instancia
Si tomamos nuestra LambdaFunctionsclase y nuestra función averageOfTwo(de la Referencia del método estático ) y la hacemos no estática, obtendremos lo siguiente:
public class LambdaFunctions {  
    double averageOfTwo(double a, double b){
        return (a+b)/2;
    }
}
Para acceder a esto, ahora necesitamos una instancia de la clase, por lo que tendríamos que hacer esto en main:
LambdaFunctions lambda = new LambdaFunctions();  
Average avg = lambda::averageOfTwo;  
System.out.println(avg.average(20.3, 4.5));  
Método de referencia del constructor
Si tenemos una clase llamada MyClassy queremos llamar a su constructor a través de una función lambda, nuestra lambda se verá así:
MyClass::new  
Aceptará tantos argumentos como pueda coincidir con uno de los constructores.

Conclusión

En conclusión, las lambdas son una característica útil para hacer nuestro código más simple, más corto y más legible.
Algunas personas evitan usarlos cuando hay muchos Juniors en el equipo, por lo que recomiendo consultar con su equipo antes de refactorizar todo su código, pero cuando todos están en la misma página, son una gran herramienta.

No hay comentarios.:

Publicar un comentario

Dejanos tu comentario para seguir mejorando!

Post Top Ad

Your Ad Spot

Páginas