FUNDAMENTOS DE PROGRAMACIÓN PRÁCTICA 18: FUNCIONES Y PREDICADOS Curso: 2010/11 Versión: 1.0.0 OBJETIVOS Aprender a generalizar determinados esquemas de tratamiento secuencial mediante el encapsulamiento de predicados y funciones. Implementar predicados y funciones. Utilizar combinaciones de funciones y predicados. PREPARACIÓN DE LA PRÁCTICA Descargue el fichero Practica18Alumno.zip de la página de la asignatura y descomprímalo en su workspace. Ejecute Eclipse e importe el proyecto que acaba de descomprimir. EJERCICIO 1: FUNCIONES 1. En este ejercicio vamos a implementar algunos métodos estáticos que trabajan sobre un array de objetos de tipo Fecha. Observaremos que, en ocasiones, el código que necesitamos para solucionar distintos problemas es muy parecido. Buscaremos entonces algún mecanismo que nos permita solucionar problemas parecidos utilizando siempre el mismo código. a) Defina un método estático getDias en la clase Fechas que reciba un array de Fecha como parámetro y devuelva un vector de Integer con la propiedad día de cada una de las fechas del array. b) Defina un método estático getMeses que reciba un array de Fecha como parámetro y devuelva un vector de Integer con la propiedad mes de cada una de las fechas del array. c) En el método main de la clase TestFechas, muestre por pantalla los días de las fechas del array fechas utilizando el método getDias de la clase Fechas. Muestre también los meses de las fechas utilizando el método getMeses. 2. Si observa el código de los métodos getDias y getMeses observará que son muy parecidos. Para poder generalizar ambos problemas y resolverlos mediante un sólo método, necesitamos encapsular el trozo de código que varía entre ambos métodos. Esto lo haremos utilizando la interfaz Function incluida en la librería Guava. public interface Function<F,T>{ T apply(F o); } Una función dispone de un método apply que permite obtener algún resultado de tipo T a partir de un objeto de tipo F. En el método getDias, para cada fecha del array, obteníamos un Integer (los días). Igualmente, en el método getMeses, para cada fecha del array obteníamos un Integer (los meses). Usando la interfaz Function podemos generalizar ambos problemas e implementarlos mediante un sólo método. a) Defina un método estático getFuncion en la clase Fechas que reciba como parámetro un array de Fecha y una Function<Fecha,Integer> y devuelva un vector de Integer con los resultados de aplicar la función a cada una de las fechas del array. Práctica 18: Funciones y predicados 2 b) En el paquete fecha, defina una clase de nombre FuncionFechaDia que implemente la interfaz Function<Fecha,Integer>. El método apply debe devolver la propiedad día de la fecha recibida como parámetro. c) En el paquete fecha, defina una clase de nombre FuncionFechaMes que implemente la interfaz Function<Fecha,Integer>. El método apply debe devolver la propiedad mes de la fecha recibida como parámetro. d) En el método main de la clase TestFechas, muestre por pantalla los días y los meses de las fechas del array fechas. Para ello, construya sendos objetos de las clases FuncionFechaDia y FuncionFechaMes e invoque adecuadamente al método getFuncion de la clase Fechas. EJERCICIO 2: PREDICADOS 1. En este ejercicio implementaremos algunos métodos estáticos más que trabajarán con arrays de fechas. Al igual que hicimos en el ejercicio 1, trataremos de encontrar algún mecanismo para generalizar problemas parecidos e implementarlos en un solo método. a) Defina un método estático existeMes en la clase Fechas que reciba como parámetro un array de Fecha y un Integer mes y devuelva true si y sólo si existe en el array alguna fecha con dicho mes. b) Defina un método estático existeAnterior en la clase Fechas que reciba como parámetro un array de Fecha y un Integer año y devuelva true si y sólo si existe en el array alguna fecha anterior a dicho año. c) En el método main de la clase TestFechas, utilice el método existeMes para decidir si el array fechas contiene alguna fecha cuyo mes sea enero. Decida también si el array contiene alguna fecha anterior a 1980, usando el método existeAnterior. 2. Si observa los métodos existeMes y existeAnterior, verá que ambos tienen la misma estructura. La única diferencia es la expresión lógica utilizada como condición de la sentencia if. Para poder generalizar ambos problemas e implementarlos mediante un único método, vamos a utilizar la interfaz Predicate incluida en la librería Guava. public interface Predicate<T>{ boolean apply(T o); } Un predicado no es más que una función que a partir de un objeto de tipo T devuelve un valor lógico. Un predicado sirve para encapsular cierta propiedad que un objeto de tipo T puede cumplir o no. En el método existeMes, se comprueba si existe alguna fecha que cumpla la propiedad de tener un mes determinado. En el método existeAnterior, se comprueba si existe alguna fecha que cumpla la propiedad de ser anterior a un año determinado. Podemos por tanto generalizar ambos métodos usando la interfaz Predicate. a) Defina un método estático existePropiedad en la clase Fechas que reciba como parámetro un array de Fecha y un Predicate<Fecha> y devuelva true si y sólo si existe alguna fecha en el array que cumpla la propiedad definida por el predicado. b) En el paquete fecha, defina una clase de nombre PredicadoFechaMes que implemente la interfaz Predicate<Fecha>. Incluya un método constructor que Práctica 18: Funciones y predicados 3 reciba un valor entero y lo almacene en un atributo mes. Defina el método apply, que debe devolver true si la fecha recibida por parámetro corresponde al mes almacenado en el atributo mes. c) En el paquete fecha, defina una clase de nombre PredicadoFechaAnterior que implemente la interfaz Predicate<Fecha>. Incluya un método constructor que reciba un valor entero y lo almacene en un atributo año. Defina el método apply, que debe devolver true si la fecha recibida por parámetro es anterior al año almacenado en el atributo año. d) En el método main de la clase TestFechas, utilizando el método existePropiedad, escriba el código necesario para decidir si existe alguna fecha en el array fechas cuyo mes sea febrero. Usando el mismo método, decida también si existe alguna fecha en el array anterior a 2000. EJERCICIO 3: ACCIONES 1. En algunas ocasiones, el trozo de código que hay que encapsular para conseguir generalizar problemas parecidos e implementarlos mediante un solo método, consiste en una o varias sentencias que no devuelven ningún objeto. Por ejemplo, podríamos tener un método que simplificara todos los racionales contenidos en un array, o un método que cambiara por un 1 el denominador de dichos racionales. Para generalizar este tipo de problemas, utilizaremos la misma interfaz del ejercicio 1 Function, haciendo que devuelva un valor de tipo Void. a) Defina un método aplicaAccion en la clase Personas que reciba como parámetros un array de Persona y una Function<Persona,Void> e invoque al método apply de la función sobre cada elemento del array. b) En el paquete persona, defina una clase de nombre AccionPersonaCumpleanyos que implemente la interfaz Function<Persona,Void>. Defina el método apply para que se incremente en uno la edad de la persona recibida como parámetro. c) En el método main de la clase TestPersonas, escriba el código necesario para incrementar en un año la edad de todas las personas del array personas, utilizando el método aplicaAccion de la clase Personas. 2. Ampliemos un poco la funcionalidad del método aplicaAccion: a) Defina un método aplicaAccion en la clase Personas que reciba, además del array de Persona y la Function<Persona,Void>, un Predicate<Persona>, de manera que el método apply será invocado sólo sobre aquellas personas del array que cumplan el predicado. b) En el paquete persona, defina una clase de nombre PredicadoPersonaMayorDe que implemente la interfaz Predicate<Persona>. Incluya un constructor que reciba un entero y lo almacene en un atributo edad. El método apply devolverá cierto si la persona recibida es mayor que la edad especificada en el atributo edad. c) En el método main de la clase TestPersonas, escriba el código necesario para hacer cumplir años a aquellas personas del array personas que sean mayores de 21 años. Práctica 18: Funciones y predicados 4 EJERCICIO 4: COMBINACIÓN DE PREDICADOS 1. A partir de predicados que tengamos implementados, puede resultarnos útil obtener nuevos predicados como combinación de ellos. En concreto, cuando necesitemos encapsular una propiedad consistente en la conjunción de otras dos propiedades que ya tenemos implementadas como predicados, podemos usar el siguiente método estático de la clase Predicates: Predicate<T> and(Predicate<? super T> first, Predicate<? super T> second); Basta invocar al método anterior pasándole como parámetros dos predicados previamente creados, p1 y p2, para obtener un predicado que sólo será cierto para aquellos objetos para los que p1 y p2 sean ciertos. a) En la clase Personas, defina un método getString que reciba un array de Persona, un Predicate<Persona> y una Function<Persona,String>, y devuelva un vector de String con el resultado de aplicar la función a aquellas personas del array que cumplan el predicado. b) Defina una clase de nombre FuncionPersonaNombre en el paquete persona, que implemente la interfaz Function<Persona,String>. El método apply debe devolver el nombre y apellidos de la persona pasada como parámetro. c) Defina una clase de nombre PredicadoPersonaMenorDe en el paquete persona, que implemente la interfaz Predicate<Persona>. Incluya un constructor que reciba un valor entero y lo almacene en un atributo edad. El método apply debe devolver true si la persona pasada como parámetro es menor que la edad almacenada en el atributo edad. d) En el método main de la clase TestPersonas, escriba las sentencias necesarias para obtener un vector con los nombres y apellidos de aquellas personas del array personas cuya edad esté comprendida entre los 20 y los 30 años. 2. También podemos combinar predicados de manera disyuntiva, usando el método Predicates.or(), con un prototipo similar al anterior método. Se obtiene con ello un predicado que será cierto para aquellos objetos para los que eran cierto alguno de los predicados combinados. a) Cree una clase de nombre PredicadoFechaVerano en el paquete fecha, que implemente la interfaz Predicate<Fecha> y cuyo método apply devuelva true si la fecha recibida como parámetro está comprendida entre el 21 de junio y el 20 de septiembre. b) En el método main de la clase TestFechas, escriba las sentencias necesarias para decidir si existe alguna fecha en el array fechas que corresponda al verano o cuyo mes sea mayo.