¿Qué es la programación funcional y cómo se aplica en Android?
La programación funcional se ha vuelto un paradigma fundamental en la creación y optimización de código en entornos como Android. Se caracteriza por permitir pasar funciones como parámetros a otras funciones, promoviendo un enfoque más claro y conciso. Un ejemplo claro es el método setOnClickListener en un botón de Android, donde este método recibe otra función como parámetro. Esta técnica permite simplificar el código y reducir su verbosidad a través del uso de lambdas.
¿Cómo se simplifican las lambdas al declararlas como variables?
Declarar lambdas como variables te permite organizar y clarificar el código. En vez de mantener bloques largos y repetitivos de código, puedes asignar lambdas a variables que mantienen el mismo comportamiento de manera más legible y estructurada. Por ejemplo:
Aquí, simplemente estás reestructurando el lambda para que sea más claro y fácil de mantener.
¿Qué es la recursividad y por qué es relevante?
En la programación funcional, la recursividad reemplaza a la iteración tradicional mediante bucles. Al no recurrir a índices, como en los bucles for, se utiliza recursividad para recorrer colecciones de manera eficiente y clara. Esto es esencial en lenguajes como Java desde la versión 8, donde el método forEach permite iterar colecciones de datos mediante funciones lambda.
¿Cómo aplicar recursividad con el método forEach?
Imagina que tienes una lista de películas en un ArrayList<Film>. Puedes recorrerlas recursivamente con forEach usando lambdas:
Esta simplicidad al recorrer colecciones y aplicar acciones demuestra el poder de la recursividad en la programación funcional.
¿Cómo puedes incluir índices sin perder eficiencia?
Para mantener una referencia de índices sin complicar el código, se puede usar AtomicInteger, que es una clase que permite manejar índices de manera más compleja pero eficiente:
AtomicInteger index =newAtomicInteger(1);films.forEach(film ->{System.out.println(index.getAndIncrement()+". "+ film.getTitle());});
Esta técnica no solo resuelve la necesidad de números únicos y consecutivos, sino que también promueve la declaración funcional del problema.
¿Qué retos nos plantea la sintaxis 2.2 en Java?
Java permite imprimir mensajes de manera más eficiente utilizando la sintaxis System.out::println. Esto plantea un reto: investigar cómo y por qué este método es útil dentro de las lambdas y qué beneficios trae en programación funcional.
Este enfoque continúa abriendo puertas al entendimiento avanzado de la programación funcional. Te animo a que investigues más sobre el uso de esta sintaxis en lambdas para profundizar tu conocimiento y manejar mejor los lenguajes que soportan estas características. ¡Sigamos aprendiendo!
La notacion Dos puntos Dobles ( :: ) como ya han explicado acá, permite acceder a un método referenciandolo directamente desde la clase a la que pertenece, ejemplo:
import java.util.*;classGFG{// static function to be called staticvoidsomeFunction(String s){System.out.println(s);}publicstaticvoidmain(String[] args){List<String> list =newArrayList<String>(); list.add("Geeks"); list.add("For"); list.add("GEEKS");// call the static method // using double colon operator list.forEach(GFG::someFunction);}}
En el ejemplo vemos que para llamar a la función someFunction hace uso de la clase GFG y la notación Dos puntos Dobles ( :: ) para referenciar directamente el método:
GFG::someFunction
Esto en realidad difiere de las Lambas debido a que se vale del llamado por referencia al método previamente definido; mientras que la Lambda lo que hace es definir directamente en la sentencia lo que va a hacer el método.
.
2. La recursividad es la forma que tiene la programación funcional para crear estructuras de repetición (que casi siempre son necesarias a la hora de resolver problemas), y cuenta con la enorme ventaja de facilitar la lectura y comprensión del código; por ejemplo una función para calcular una sumatoria usando recursividad es tan simple y fácil de entender como esto:
publicstatic int sumaN_recursivo(int n){return(n<=0)?0: n +sumaN_recursivo(n-1);}
Que en cambio se torna un poco más compleja de entender cuando en vez de usar la recursividad usamos estructuras de bucle o iteraciones (while, for). El ejemplo anterior usando iteración queda así:
publicstatic int sumaN_iterativo(int n){ int result=0;for(int i=1; i<=n; i++){ result +=i;}return result;}
Como se darán cuenta tratar de hacer seguimiento de lo que hace el ciclo for y por tanto entenderlo resulta más complejo (o al menos les tomará más tiempo) que el código del ejemplo de la recursividad.
.
Sin embargo existe una razón muy poderosa por la que casí nunca se prefiere el uso de la recursividad por sobre la interación y es el error de tipo StackOverflowError, que se debe al enorme gasto de tiempo y espacio en memoria que normalmente implica que una función se llame a sí misma (recursividad). Concretamente en los casos en que los datos a procesar son significativamente grandes (o se requieren muchas repeticiones del código) los códigos recursivos tienden a "reventar" la aplicación debido a que la "Pila de datos a trabajar Desborda la capacidad de la máquina" o lo que es lo mismo StackOverflowError.
.
Nota: Ejemplos tomados de
es otra forma de poder imprimir un listado.
Se puede hacer lo mismo con esta sintaxis:
lista.forEach(n ->System.out.print(n +" "));
En este ultimo se le pueden agregar mas cosas.
Otra diferencia es que println deja un salto de linea después de imprimir en pantalla el texto, y print no lo hace después de imprimir el texto en pantalla.
Las referencias a los métodos nos permiten reutilizar un método como expresión lambda. Para hacer uso de las referencias a métodos basta con utilizar la siguiente sintáxis: referenciaObjetivo::nombreDelMetodo.
(String info) -> System.out.println(info) // Expresión lambda sin referencias.
System.out::println // Expresión lambda con referencia a método estático.
Igualmente no me queda del todo claro.
Muchas veces depende del tipo de función y del contexto, hay funciones que por su grado de complejidad tienen menos carga haciendo una iteración que creando una función recursiva.
Bueno es que recursividad e iteración son dos cosas distintas. Por un lado iterar significa recorrer algo desde un punto hasta otro punto. En el caso de los ejemplos que hemos visto hemos estado haciendo iteraciones internas dentro de una lista de elementos de un tipo de dato definido por el usuario.
En el caso de la recursividad es cuando tenemos la necesidad de recorrer internamente una estructura con un método que se llama a si mismo. Esto lo podemos encontrar por ejemplo cuando recorremos un árbol binario, por ejemplo para ordenarlo (Si es que es posible ordenarlo, dependería del tipo de dato que se encuentre dentro del arbol, por ejemplo si es numerico o alfabético)
Creo que no hemos hecho recursividad en Lambdas hasta ahora. Creo… Puede que me equivoque pero hasta el momento no me parece haber visto la recursividad.
Un ejemplo de método recursivo sería por ejemplo
private int findValue(ArrayList<int> values, int startingPosition, int value){
if(values.get(startingPosition) == value)
return values.get(startingPosition);
else
return findValue(values, startingPosition + 1, value);
}
En este ejemplo tenemos un metodo que recorre de manera recursiva un arreglo buscando un elemento. Notas como no hacemos iteraciones para recorrer el arreglo?
En un caso donde no usaramos recursividad hariamos:
public int findValue(ArrayList<int> values, int value){
for(int i = 0; i <= values.size() - 1; i++){
if(values.get(i)==value)
return value;
}
return 0;
}
En este ultimo ejemplo usamos iteraciones en lugar de recursividad.
Existen diferencias de rendimiento entre la recursividad y la iteracion. Como puedes ver la recursividad no tiene nada del otro mundo pero es mas simple digamos de entender el procedimiento iterativo.
Cuando haces recursividad debes tener ciertos cuidados de consideracion, por ejemplo:
-Considera la condicion de terminacion
-Considera que debes devolver la misma funcion cuando la condicion de terminacion no se cumpla.
De cierto modo hacer una funcion recursiva es mas eficiente que hacer una iteracion porque estas haciendo menos condiciones, en la iteracion debes corroborar en cada iteracion que el ciclo termine, pero casi que viene siendo lo mismo.
Hay problemas que solo podemos resolver haciendo recursividad eso si. Este tan simple del ejemplo no hace falta, es solo un ejemplo.
Es debido que el metodo println es estatico en la declarion de la clase System.out
por eso en java 8 en adelante se puede llamar a los metod estaticos con la notacion :: ademas de que el metodo va a buscar el metodo toString() del objeto, se necesitaria sobreescribir el metodo toString de cada clase del proyecto de amazonviewer
Bueno en este caso el uso de Clase::Método permitiría hacer un uso corto de un método estático de una clase para devolver un resultado, esta implementación puede usarse con un lambda o fuera de un lambda.
Por ejemplo para un lambda si nosotros tenemos por ejemplo:
ArrayList<String> palabras = this.returnRandomWords();
//Podriamos hacer algo como esto:
palabras.foreach(System.out::println)
//De manera que println imprimiria todas las palabras de la lista.
Ahora bien, no me queda claro como trabajar un lambda con una funcion que espera mas de un parametro, en este caso printLn funciona porque imprime en pantalla, pero si tiviesemos una funcion que devolviese un resultado y debamos trabajar con ese resultado en otra funcion que hariamos? la recibimos como parametro en otra funcion dentro del lambda?
Segun la maestra en el minuto 2:30 es posible crear lambdas a partir de clases abstractas con un solo metodo abstracto, pero eclipse me marca error diciendo que debe ser a fuerzas una functional interface. Lo mismo encontre investigando en google. Hay una manera de hacer esto con clases abstractas?
AtomicInteger atomicInteger =newAtomicInteger(1); movies.forEach(m ->System.out.println(atomicInteger.getAndDecrement()+". "+ m.getTitle()+" Visto: "+ m.isViewed()));```
pero a la hora de imprimir los "indicices" no lo hace bien
Coco Visto: Sí
Titanic Visto: Sí
-1. Avatar Visto: Sí
-2. Amélie Visto: Sí
-3. Sombra del Agua Visto: No
permite mandar este parametro a la funcion siguiente, en este caso println.
No me quedo 100% claro pero si trato de explicarlo en mis palabras seria que utilizando los dos puntos, el metodo System.out.println sera llamado con el parametro recibido del foreach, sin tener que especificar el parametro ni la utilizacion de este. Es decir, solamente tendriamos que escribir con los dos puntos la funcion que nosotros queremos que se ejecute cada vez que el foreach nos da un resultado y que al mismo tiempo es el parametro de la funcion.
Es una referencia de metodo y hay 4 tipos distintos, no aplica en todos los casos de funciones lambda ya que depende del numero de parametros que la funcion a referenciar reciba.
Notas: AtomicInteger atomicInteger = new AtomicInteger(1);
atomicInteger.getAndIncrement();
Excelente explicación, nuevo tema aprendido de los AtomicIntegers
: : es un operador de referencia de método en Java, es utilizado para llamar a un método, en este caso no especifica explícitamente el parámetro en el método llamado.