Hasta ahora, Java apoyaba el estilo imperativo de programación y el estilo de programación orientado a objetos. La siguiente gran cosa que se ha agregado java es que Java ha comenzado a admitir el estilo funcional de programación con su versión Java 8. En este artículo, discutiremos la programación funcional en Java 8.
¿Qué es la programación funcional?
Es un estilo declarativo de programación en lugar de imperativo. El objetivo básico de este estilo de programación es hacer que el código sea más conciso, menos complejo, más predecible y más fácil de probar en comparación con el estilo de codificación heredado. La programación funcional se ocupa de ciertos conceptos clave, como la función pura , el estado inmutable , la programación sin asignación, etc.
Programación funcional frente a programación puramente funcional:
los lenguajes de programación puramente funcionales no permiten ninguna mutabilidad en su naturaleza, mientras que un lenguaje de estilo funcional proporciona funciones de orden superior, pero a menudo permite la mutabilidad a riesgo de que no hagamos las cosas correctas, lo que supone una carga. en nosotros en lugar de protegernos. Entonces, en general, podemos decir que si un idioma proporciona una función de orden superior, es un lenguaje de estilo funcional, y si un idioma llega al extremo de limitar la mutabilidad además de la función de orden superior, entonces se convierte en un lenguaje puramente funcional. Java es un lenguaje de estilo funcional y el lenguaje como Haskell es un lenguaje de programación puramente funcional.
Entendamos algunos conceptos en programación funcional :
- Funciones de orden superior: en la programación funcional, las funciones deben considerarse como ciudadanos de primera clase. Es decir, hasta ahora en el estilo heredado de codificación, podemos hacer cosas a continuación con objetos.
- Podemos pasar objetos a una función.
- Podemos crear objetos dentro de la función.
- Podemos devolver objetos desde una función.
- Podemos pasar una función a otra función.
- Podemos crear una función dentro de la función.
- Podemos devolver una función desde una función.
- Funciones puras : una función se llama función pura si siempre devuelve el mismo resultado para los mismos valores de argumento y no tiene efectos secundarios como modificar un argumento (o variable global) o generar algo.
- Expresiones lambda: una expresión lambda es un método anónimo que tiene mutabilidad mínima y solo tiene una lista de parámetros y un cuerpo. El tipo de devolución siempre se deduce en función del contexto. Además, tenga en cuenta que las expresiones Lambda funcionan en paralelo con la interfaz funcional. La sintaxis de una expresión lambda es:
(parameter) -> body
- En su forma simple, una lambda podría representarse como una lista de parámetros separados por comas, el símbolo –> y el cuerpo.
¿Cómo implementar la programación funcional en Java?
java
// Java program to demonstrate // anonymous method import java.util.Arrays; import java.util.List; public class GFG { public static void main(String[] args) { // Defining an anonymous method Runnable r = new Runnable() { public void run() { System.out.println( "Running in Runnable thread"); } }; r.run(); System.out.println( "Running in main thread"); } }
Running in Runnable thread Running in main thread
Si observamos los métodos run() , lo envolvimos con Runnable. Estábamos inicializando este método de esta manera hasta Java 7. El mismo programa se puede reescribir en Java 8 como:
java
// Java 8 program to demonstrate // a lambda expression import java.util.Arrays; import java.util.List; public class GFG { public static void main(String[] args) { Runnable r = () -> System.out.println( "Running in Runnable thread"); r.run(); System.out.println( "Running in main thread"); } }
Running in Runnable thread Running in main thread
Ahora, el código anterior se ha convertido en expresiones Lambda en lugar del método anónimo. Aquí hemos evaluado una función que no tiene ningún nombre y esa función es una expresión lambda. Entonces, en este caso, podemos ver que una función ha sido evaluada y asignada a una interfaz ejecutable y aquí esta función ha sido tratada como ciudadano de primera clase.
Refactorización de algunas funciones de Java 7 a Java 8:
hemos trabajado muchas veces con bucles e iteradores hasta Java 7 de la siguiente manera:
java
// Java program to demonstrate an // external iterator import java.util.Arrays; import java.util.List; public class GFG { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(11, 22, 33, 44, 55, 66, 77, 88, 99, 100); // External iterator, for Each loop for (Integer n : numbers) { System.out.print(n + " "); } } }
11 22 33 44 55 66 77 88 99 100
Arriba había un ejemplo de bucle forEach en Java , una categoría de iterador externo, debajo hay un nuevo ejemplo y otra forma de iterador externo.
java
// Java program to demonstrate an // external iterator import java.util.Arrays; import java.util.List; public class GFG { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(11, 22, 33, 44, 55, 66, 77, 88, 99, 100); // External iterator for (int i = 0; i < numbers.size(); i++) { System.out.print(numbers.get(i) + " "); } } }
11 22 33 44 55 66 77 88 99 100
Podemos transformar los ejemplos anteriores de un iterador externo con un iterador interno introducido en Java 8, de la siguiente manera:
java
// Java 8 program to demonstrate // an internal iterator import java.util.Arrays; import java.util.List; public class GFG { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(11, 22, 33, 44, 55, 66, 77, 88, 99, 100); // Internal iterator numbers.forEach(number -> System.out.print( number + " ")); } }
11 22 33 44 55 66 77 88 99 100
Aquí, la interfaz funcional juega un papel importante. Dondequiera que se espere una única interfaz de método abstracto, podemos pasar la expresión lambda muy fácilmente. El código anterior podría simplificarse y mejorarse más de la siguiente manera:
numbers.forEach(System.out::println);
Programación Imperativa Vs Declarativa:
El estilo funcional de programación es la programación declarativa. En el estilo imperativo de la codificación, definimos qué hacer en una tarea y cómo hacerlo. Mientras que, en el estilo declarativo de codificación, solo especificamos qué hacer. Entendamos esto con un ejemplo. Dada una lista de números, averigüemos la suma del doble de los números pares de la lista usando un estilo de codificación imperativo y declarativo.
java
// Java program to find the sum // using imperative style of coding import java.util.Arrays; import java.util.List; public class GFG { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(11, 22, 33, 44, 55, 66, 77, 88, 99, 100); int result = 0; for (Integer n : numbers) { if (n % 2 == 0) { result += n * 2; } } System.out.println(result); } }
640
El primer problema con el código anterior es que estamos mutando el resultado de la variable una y otra vez. Entonces, la mutabilidad es uno de los mayores problemas en un estilo imperativo de codificación. El segundo problema con el estilo imperativo es que dedicamos nuestro esfuerzo a decir no solo qué hacer, sino también cómo realizar el procesamiento. Ahora reescribamos el código anterior en un estilo declarativo.
java
// Java program to find the sum // using declarative style of coding import java.util.Arrays; import java.util.List; public class GFG { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(11, 22, 33, 44, 55, 66, 77, 88, 99, 100); System.out.println( numbers.stream() .filter(number -> number % 2 == 0) .mapToInt(e -> e * 2) .sum()); } }
640
Del código anterior, no estamos mutando ninguna variable. En cambio, estamos transformando los datos de una función a otra. Esta es otra diferencia entre imperativo y declarativo. No solo esto, sino también en el código anterior de estilo declarativo, cada función es una función pura y las funciones puras no tienen efectos secundarios.
En el ejemplo anterior, estamos duplicando el número con un factor de 2, que se llama Cierre . Recuerde, las lambdas no tienen estado y el cierre tiene un estado inmutable. Significa que, en cualquier circunstancia, el cierre no puede ser mutable. Entendámoslo con un ejemplo. Aquí declararemos un factor variable y lo usaremos dentro de una función como se muestra a continuación.
java
// Java program to demonstrate an // declarative style of coding import java.util.Arrays; import java.util.List; public class GFG { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(11, 22, 33, 44, 55, 66, 77, 88, 99, 100); int factor = 2; System.out.println( numbers.stream() .filter(number -> number % 2 == 0) .mapToInt(e -> e * factor) .sum()); } }
640
El código anterior funciona bien, pero ahora intentemos mutarlo después de su uso y veamos qué sucede:
java
import java.util.Arrays; import java.util.List; public class GFG { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(11, 22, 33, 44, 55, 66, 77, 88, 99, 100); int factor = 2; System.out.println( numbers.stream() .filter(number -> number % 2 == 0) .mapToInt(e -> e * factor) .sum()); factor = 3; } }
El código anterior da un error en tiempo de compilación que dice que el factor de variable local definido en un ámbito adjunto debe ser final o efectivamente final.
Esto significa que aquí, el factor variable se considera por defecto como definitivo. En resumen, nunca debemos intentar mutar ninguna variable que se use dentro de funciones puras. Si lo hace, violará las reglas de funciones puras que dicen que la función pura no debe cambiar nada ni depender de nada que cambie. La mutación de cualquier cierre (factor aquí) se considera un cierre malo porque los cierres son siempre de naturaleza inmutable.
Publicación traducida automáticamente
Artículo escrito por asadaliasad y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA