Proyecto y ejercicios¶
En este proyecto se llevará acabo una serie de ejercicios sobre la programación funcional.
IMPORTANTE
Sólo se creará un único proyecto, para cada ejercicio se deberá crear una nueva versión del mismo a través de GitHub, teniendo que usar tags para cada versión cuyo nombre sea vX.0 dónde X es el número de ejercicio
Ejercicio 1
Crear una interfaz Predicate
, que tenga un único método llamado test(entero)
que reciba un entero y retorne un booleano.
Crea una clase llamada IsPrime
que implemente la interfaz Predicate
y cuyo método test(entero)
retorne true si el entero recibido es un número primo.
Crea una clase SuperFunctions
, que posea los siguientes métodos estáticos:
count(list, predicate)
: Retorna el número de elementos de la lista que cumplen con el predicado recibido. En elmain
llama a dicho método estático para obtener cuántos números hay que sean primos en una lista pasada como argumento. Así, si la lista es[5, 7, 2, 4, 9, 5, 15, 3]
, el método estático debe retornar5
, ya que contiene los siguientes números primos5, 7, 2, 5, 3
.allMatch(list, predicate)
: Retorna true si todos los elementos de la lista cumplen con el predicado recibido. En elmain
llama a dicho método estático para obtener si todos los números de la lista son primos.noneMatch(list, predicate)
: Retorna true si ningún elemento de la lista cumple con el predicado recibido. En el main llama a dicho método estático para obtener si ninguno de los números de la lista es primo.anyMatch(list, predicate)
: Retorna true si algún elemento de la lista cumple con el predicado recibido. En elmain
llama a dicho método estático para obtener si alguno de los números de la lista es primo.takeWhile(list, predicate)
: Retorna una nueva lista creada a partir de los elementos de la lista recibida como argumento, de manera que va tomando valores de la lista original mientras éstos cumplan con el predicado recibido. En cuanto un elemento no cumple el predicado se dejan de incluir elementos en la lista resultante. Por ejemplo, si le pasamos la lista[5, 7, 2, 4, 9, 5, 15, 3]
y el predicado de que el número sea primo, la función deberá retornar la lista[5, 7, 2]
, ya que el4
ya no es primo. Prueba el ejemplo anterior desde elmain
.dropWhile(list, predicate)
: Retorna una nueva lista creada a partir de los elementos de la lista recibida como argumento, de manera que el primer elemento que es incluido en la lista resultante es aquel que no cumple con el predicado recibido como argumento, y a partir de dicho elemento el resto de elementos de la lista original son incluidos sin realizar ninguna comprobación. Por ejemplo, si le pasamos la lista[5, 7, 2, 4, 9, 5, 15, 3]
y el predicado de que el número sea primo, la función deberá retornar la lista[4, 9, 5, 15, 3]
, porque el primer número no primo de la lista original es4
. Prueba el ejemplo anterior desde el main .
Ejercicio 2
Crea una interfaz Function
, que tenga un único método llamado apply(entero)
que reciba un entero y retorne un entero.
Crea una clase llamada Triple
que implemente la interfaz Function
y cuyo método apply(entero)
retorne el triple del entero recibido.
Crea una clase llamada LessThan100
que implemente la interfaz Predicate
y cuyo método test(entero)
retorne true si el entero recibido es >= 0 y menor que 100.
En la clase SuperFunctions agrega los siguientes métodos estáticos:
iterate(integerSeed, count, function)
: Retorna una lista concount
elementos (se debe lanzar una excepción sicount <= 0
), donde el primer elemento corresponde aintegerSeed
y el resto de elementos se calcula aplicando la función recibida al elemento anterior. Por ejemplo, siintegerSeed
es 1,count
es 4 y la función es calcular el triple, el método debe retornar la lista[1, 3, 9, 27]
iterate(integerSeed, max, predicate, function)
: Retorna una lista donde el primer elemento corresponde aintegerSeed
y el resto de elementos se calcula aplicando la función recibida al elemento anterior. En cuanto un elemento generado no cumple con el predicado dicho elemento no es incluido en la lista resultante y ésta se da por concluida. Si la lista resultante alcanza el número máximo de elementosmax
la lista se da por concluida. Por ejemplo, siintegerSeed
es 1,max
es 10, el predicado es que el número sea menor que 100 y la función es calcular el triple, el método debe retornar la lista[1, 3, 9, 27, 81]
, ya que 243 (81 * 3) ya no es menor que 100. Si el predicado se cumple siempre, como por ejemplo si en el caso anteriorintegerSeed
es 0 , se retornará una lista cuyo tamaño corresponderá amax
, en nuestro caso[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
.
Ejercicio 3
Crea una interfaz ConsumerBinary
, que tenga un único método accept(entero1,entero2)
que reciba dos enteros y no retorne nada.
Crea una clase PrinterWithIndex
que implemente la interfaz ConsumerBinary
y cuyo método accept(entero1, entero2)
muestre por consola el mensaje "El valor {entero2} tiene el índice {entero1}" sustituyendo {entero2}
por el valor de entero2 y {entero1} por el valor de{entero1}
Crear una interfaz FunctionBinary
, que tenga un único método apply(entero1, entero2)
que reciba dos enteros y retorne un entero.
Crea una clase Subtractor
que implemente dicha interfaz y cuyo método apply()
retorne el resultado de restarle entero1 a entero2.
En la clase SuperFunctions
agrega los siguientes métodos estáticos:
actWithIndex(list, consumerBinary)
: Para cada elemento de la lista llama al métodoaccept
del consumidor binario pasándole como primer argumento el índice del elemento y como segundo argumento el elemento en sí. La función debe retornar una nueva lista con los elementos de la lista original. Por ejemplo, si la lista es[10, 20, 30]
y elconsumerBinary
es un objeto de la clasePrinterWithIndex
, se debe mostrar por consola:
[10, 20, 30]
. consumeWithIndex(list, consumerBinary)
: Para cada elemento de la lista llama al métodoaccept
del consumidor binario pasándole como primer argumento el índice del elemento y como segundo argumento el método en sí. La función no debe retornar nada.reduceFromEnd(list, functionBinary)
: Retorna un valor entero con el resultado de aplicar la operación de reducción indicada por lafunctionBinary
pero tomando como primer elemento el último de la lista original. Si la lista original estuviera vacía retornaránull
. Por ejemplo, si la lista es[1, 3, 9, 27]
y la función binaria corresponde a un objeto de la claseSubtractor
, la función debe retornar 14, que corresponde a la operación 27 - 9 - 3 - 1.reduceFromEnd(list, identity, functionBinary)
: Retorna un valor entero con el resultado de aplicar la operación de reducción indicada por lafunctionBinary
pero tomado como primer elemento el último de la lista original. El valor inicial de acumulador corresponderá aidentity
. Por ejemplo, si la lista es[1, 3, 9, 27]
, identidad corresponde0
y la función binaria corresponde a un objeto de la claseSubtractor
, la función debe retornar -40, que corresponde a la operación 0 - 27 - 9 - 3 - 1.
Ejercicio 4
Realiza las siguientes modificaciones:
- En el main(), realiza las llamadas anteriores a las funciones haciendo uso de las clases anónimas inline, en vez de usar las clases
IsPrime
,Triple
,NaturalLessThan100
,PrinterWithIndex
oSubtractor
, que puedes eliminar tranquilamente. - Modifica las interfaces para que en vez de trabajar con enteros, sean interfaces genéricas parametrizadas que funcionen con tipos genéricos.
- Modifica los métodos de la clase
SuperFunction
para que se adapten a los cambios descritos anteriormente. - Vuelve a cambiar en el main(), las llamadas a las funciones, haciendo uso de las clases anónimas inline, pero en esta ocasión haciendo uso de los parametrizadas con el tipo adecuado.
Ejercicio 5
Crea la clase Stream<T>
, que representa un flujo, y añádele los métodos de la clase SuperFunctions
, sin que sean estáticos (excepto el método iterate
). (La clase SuperFunctions
podrá ser eliminada).
Haz que los métodos de la clase retornen un nuevo flujo en vez de una lista.
Agrega a la clase Stream<T>
los métodos:
filter(predicate)
: retorna un nuevo flujo con los elementos de la lista original que cumplan el predicado.map(function)
: retorna un nuevo flujo con los elementos "transformados" del flujo original. Por ejemplo, si la flujo inicial está formado por los números del 1 al 10 y se quiere transformar al cuadrado de dichos números (el valor defunction
sería una clase anónima inline, que la implementación de su métodoapply
retorne el cuadrado de un número).of(vararg)
: método estático que recibe un vararg de elementos, que retornará un flujo con los elementos recibidos.from(collection)
: método estático que recibe una colección de elementos y retornará un nuevo flujo con los elementos recibidos.recolectar()
: retorna una lista con los elementos gestionados por el flujo.
Realiza un main que cree un flujo usando el método estático iterate(integerSeed, count, function)
, donde integerSeed
, sea 1, count
sea 5 y function
sea una Function<Integer>
cuyo método apply()
retorna el valor recibido multiplicado por 5 y se le sume 1. Después el flujo generado será filtrado de manera que se obtenga un nuevo flujo con sólo los valores del anterior que sean pares. Después se deberá obtener un nuevo flujo con los valores del anterior convertidos a cadena con el formato -valor-
y finalmente dicho flujo se reduzca haciendo uso del método reduceFromEnd()
, de manera que se obtenga una única cadena de caracteres. Por ejemplo -156--6-
.
Ejercicio 6
Realiza la versión anterior modificando lo siguiente:
- Añade la anotación
@FunctionalInterface
a todas las interfaces definidas. - Reemplaza las clases anónimas con expresiones lambdas.
Ejercicio 7
En esta versión elimina las interfaces funcionales y reemplázalas en la clase Stream<T>
por las interfaces funcionales predefinidas en Java 8.
Ejercicio 8
Crea una clase MyAppUtils
con los siguientes métodos estáticos:
quintupleMoreOne(value)
: recibe un entero y retorna dicho valor multiplicado por 5 y después sumándole 1.isEven(value)
: recibe un entero y retorna true si el número es par.withDashes(value)
: recibe un entero y retorna la cadena-value-
.concat(str1, str2)
: retorna la concatenación de ambas cadenas recibidas.
Haciendo uso de la referencias a métodos, sustituye las expresiones lambda por la llamada a estos métodos.
Ejercicio 9
Realiza los métodos anteriores dentro de la misma clase Main sin definirlos de forma estática y eliminado la clase MyAppUtils
. Además en vez de usar el método concat
(que puede ser eliminado) se usará el método concat
de la clase String
.
Ejercicio 10
Agrega a la clase Stream<T>
un método find(predicate)
que encuentre el primer elemento que cumple con el predicado y lo retorne en un Optional<T>
.
Agrega también, un método llamada findLast(predicate)
que encuentre el último elemento que cumple con el predicado y lo retorne en un Optional<T>
.