Introducción a la programación Funcional

1

¿Qué es la Programación Funcional?

Entendiendo las partes de la programación funcional

2

¿Qué es una función en Java?

3

Funciones como ciudadanos de primera clase

4

Funciones puras

5

Entendiendo los efectos secundarios

6

Funciones de orden mayor

7

Funciones lambda

8

Inmutabilidad

Functional Programming en Java

9

Repositorio del curso

10

Configuración del entorno de trabajo

11

Revisando el paquete java.util.function: Function

12

Revisando el paquete java.util.function: Predicate

13

Revisando el paquete java.util.function: Consumer y Supplier

14

Revisando el paquete java.util.function: Operators y BiFunction

15

Entendiendo dos jugadores clave: SAM y FunctionalInterface

16

Operador de Referencia

17

Analizando la inferencia de tipos

18

Comprendiendo la sintaxis de las funciones lambda

19

Usando metodos default en nuestras interfaces

20

Dándole nombre a un viejo amigo: Chaining

21

Entendiendo la composición de funciones

Optional y Streams: Datos mas interesantes

22

La clase Optional

23

Entendiendo los Streams

24

¿Qué son los Stream listeners?

25

Operaciones y Collectors

26

Streams de tipo específico y Paralelismo

27

Operaciones Terminales

28

Operaciones Intermedias

29

Collectors

Todo junto: Proyecto Job-search

30

job-search: Un proyecto para encontrar trabajo

31

Vista rápida a un proyecto de Gradle

32

Revisando las opciones para nuestro CLI

33

Librerías adicionales para nuestro proyecto

34

Entendiendo la API de jobs

35

Diseñando las Funciones Constructoras de nuestro Proyecto

36

Agregando validaciones de datos

37

Diseñando las funciones de transformacion de datos

38

Creando flujos extras de transformación de Datos

Conclusiones

39

Un repaso a lo aprendido

No tienes acceso a esta clase

¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera

Usando metodos default en nuestras interfaces

19/39
Recursos

Aportes 17

Preguntas 1

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad?

Personalmente me confundí un poco con esta clase porque no había visto antes una interfaz funcional con más de un método. Por eso me puse a investigar un poco más en los docs y se los comparto:

Escencialmente una interfaz con la anotación @FunctionalInterface solo puede tener 1 método abstracto. Como los métodos default tienen una implementación no son abstractos y por lo tanto no rompen el contrato de @FunctionalInterface.

También me surgió una duda respecto al método asociado a la lambda: ¿podríamos hacer StringOperation op = text -> {} (asociar la lambda a operate(String) en lugar de a getAmout())?. La respuesta es inmediata y obvia: No. Si pudiesemos hacer lo anterior getAmount() no tendría una definición y eso es imposible, generaría un error de compilación.

Default nos da la posibilidad de definir un comportamiento base para la interface y asi definir a su vez que parametros seran necesarios.

Por medio de los métodos default en las interfaces vamos a poder crear una estructura base para las funciones, en especial pre-establecer que parámetros tomaran.
 
Muchas gracias por la explicación instructor Sinuhé.

métodos default


Al definir estas interfaces personalizadas, es posible incluir métodos default, estos son métodos cuyo código sí está escrito en la definición de la interfáz.

Por ejemplo, la interfáz NumberListOperation posee el método default operarLista el cual recibe una lista, y devuelve una nueva lista donde cada elemento es el resultado de aplicar la funcion operacion sobre los elementos de la lista original.

@FunctionalInterface
interface NumberListOperation {
    Integer operacion(Integer x);

    default List<Integer> operarLista(List<Integer> numeros){
        List<Integer>  resultado = new ArrayList<>();
        for (Integer n : numeros) {
            Integer num = operacion(n);
            resultado.add(num);
        }
        return resultado;
    }
}

Dicha interfáz puede usarse para obtener el doble de cada uno de los números de una lista.

List<Integer> numeros = Arrays.asList(1, 2, 3, 4, 5);

NumberListOperation duplicar = x -> x * 2;
numeros = duplicar.operarLista(numeros);

// ahora numeros contiene 2, 4, 6, 8, 10

Default
Es una keyword que permite añadir un comportamiento por defecto a la interfaz.

En una Functional Interface, tendremos un método abstracto (sin implementar) y al momento de crear una instancia de esa interface, al pasarle una lambda, esa será el comportamiento que tomará.

En el siguiente ejemplo que cree:

 public static void main(String[] args) {
        StringOperations six = () -> 6;
        six.operate("Alumno");
        StringOperations duplicate = () -> six.getAmount() * 2;
        duplicate.operate("Calfiicacion");
    }

En la instancia llamada six, se imprimirá la palabra _alumno _ 6 veces. Pero en la instancia llamada duplicate, tomará el valor de la primera instancia y lo duplicará, haciendo que el valor Calificacion se imprimirá 12 veces.

Un error que me ocurrió cuando experimenté con los métodos default fue cuando intenté hacer que una clase implementara dos interfaces que tenían la misma cabecera del método default. Java reconoce que hay un cuerpo y no nos permite continuar la compilación si no se resuelve.

Como las interfaces son abstracciones de comportamiento, Java nos permite hacer una sobre-escritura del método y hacer como que no hay cuerpo en ninguno de los métodos default para resolver esta ambigüedad. Esto es lo que pasaría (frecuentemente) si se permitiera herencia múltiple en Java.

Default en una interfaz nos permitirá implementar un método.

@FunctionalInterface, esta anotación nos permite contar con método sin definir y también método default.

Muy buenas ideas de aplicación al final de la clase

El concepto del `default` tiene aplicaciones muy interesantes. Para entenderlo es imperativo orientar el razonamiento hacia el paradigma funcional. La costumbre del paradigma orientado a objetos nos confunde. El `default` permite la definición de capacidades adicionales para la `@FunctionalInterface` que estamos construyendo; ya que el comportamiento de la función en sí solo será definido cuando usemos la interfaz y ejecutado por su método abstracto. Se me hizo fácil entenderlo explorando la interfaz `Consumer`. Ésta por definición recibe un valor como parámetro y no devuelve nada. Lo cual está representado en su método abstracto: `void accept(T t);` Simplemente "acepta" el parámetro y lo usa para la función declarada, en éste caso será de tipo `void`. Pero cuenta con el método `default` llamado `andThen` que recibe como parámetro otro `Consumer`. ```java default Consumer<T> andThen(Consumer after) { Objects.requireNonNull(after); return (T t) -> { accept(t); after.accept(t); }; } ``` De esa manera ejecuta ambas funciones para el mismo parámetro. En otras palabras, le está proporcionando la capacidad a la interfaz `Consumer` de decidir qué hacer después (a través de otra función del tipo `Consumer`) de ejecutar la función declarada. Encadena dos ejecuciones de tipo `void`. Un ejemplo que se me ocurre sería notificar a un usuario acerca de un evento en el sistema "y luego" logear la ejecución de la notificación (Para efectos prácticos, omitamos las validaciones asociadas a la notificación de un usuario): ```java public class UserNotifier { public static void main(String[] args) { String username = "JohnDoe"; Consumer<String> notify = user -> System.out.println("Notificar al usuario: " + user); Consumer<String> log = user -> System.out.println("El usuario: " + user + " ha sido notificado."); Consumer<String> notifyAndLog = notify.andThen(log); notifyAndLog.accept(username); } } ```
Al final de la clase, se dan ejemplos del mundo real donde se puede implementar una interfaz funcional con métodos default, recomendaría que los ejemplos que dan durante las explicaciones en las clases no sean cosas tan genéricas como "DoOperation" o "StringOperation", es más fácil entender las cosas cuando los ejemplos que se utilizan guardan similitudes con escenarios de la vida real.

wooww estuvo heavy. pero muy interesante

Son comportamientos, que puede tener una interfaz por defecto:

Por ejemplo, si la interfaz, hace queries, un comportamiento por defecto:

  • Establecer una conexión.
  • Verificar los datos del query.
  • Mapear las respuestas del query.
    @FunctionalInterface
    interface StringInterface {
        int getValue();
        default void validateValue(String s) throws Exception {
            if (s.isBlank() || s.isEmpty()){
                System.out.println("Error bro");
                throw new Exception();
            }
        }
    }

    public static void main(String[] args) throws Exception {
        StringInterface v = () -> 6;
        v.validateValue("");
    }

default nos permite crear un metodo vacio o no devuelve nada

Super 😃

Genial