Você está na página 1de 8

Programación Orientada a Objetos

Curso 2015/2016

Ejercicios sobre colecciones y Java 8


Previo

Descárgate de la sección de prácticas de la página web de la asignatura


(dis.um.es/docencia/poo) el proyecto “Colecciones-Java8” e impórtalo en Eclipse. Este
proyecto contiene la implementación del problema de la subasta. El enunciado de este
problema también lo puedes encontrar en la web.
A la clase Usuario se le han añadido dos atributos que no se contemplan en el problema de
la subasta (fecha de nacimiento y correo electrónico) para favorecer la definición de los
ejercicios relacionados con las nuevas propiedades de Java 8.

Colecciones ordenadas

Añade al paquete pruebas del proyecto que te has descargado, la clase


PruebaColeccionesOrdenadas e incluye el método main para realizar los siguientes
ejercicios.
Conjuntos ordenados:
- Declara y construye un conjunto ordenado (TreeSet) que almacene cadenas (String).
- Introduce varias cadenas en el conjunto.
- Realiza un recorrido (for each) del conjunto y comprueba que el iterador retorna los
elementos ordenados según el orden lexicográfico de las cadenas (orden alfabético).
Este comportamiento se debe a que las cadenas implementan la interfaz
Comparable<T>, es decir, tienen un orden natural.
- Implementa el orden natural de la clase Usuario. El orden natural debe basarse en el
orden alfabético del nombre de usuario. Utiliza el método compareTo de la clase String
para comparar los nombres de los usuarios.
- Declara y construye un conjunto ordenado de usuarios.
- Construye varios usuarios y añádelos al conjunto. Observa que si introducimos varios
usuarios con el mismo nombre sólo el primero que se inserte permanece en el conjunto.
Un conjunto ordenado determina que un elemento es repetido si en la comparación
obtiene el valor 0. Por tanto, es recomendable que la implementación del orden natural
sea consistente con la igualdad (equals).
- Recorre el conjunto y muestra los usuarios. Observa que el iterador retorna los
elementos según el orden natural.
- Implementa un comparador de usuarios (interfaz java.util.Comparator<T>) que
ordene los usuarios según el orden alfabético inverso de sus nombres.
- Declara y construye un conjunto ordenado de usuarios que aplique el orden
implementado por el comparador anterior. Añade usuarios a la colección (método
addAll) y realiza un recorrido para comprobar que el orden es el correcto.
- Utiliza los métodos sort de la clase Collections para ordenar la lista, según el orden
natural y con el comparador de orden inverso. Muestra el contenido de la lista tras
ambas ordenaciones.

1/8
Mapas con claves ordenadas:
- Declara y construye un mapa ordenado (TreeMap) que asocie cadenas (String) con
objetos usuario.
- Recorre el último conjunto ordenado e inserta en el mapa los objetos del conjunto. Utiliza
el nombre del usuario como clave en el mapa.
- Recorre las claves del mapa ordenado (método keySet) y muestra por la consola las
claves. Observa que el mapa retorna las entradas ordenadas por clave. Por defecto,
utiliza el orden natural de la clase que actúa de clave (String).
- Implementa un comparador de cadenas que implemente el orden alfabético inverso.
Para ello utiliza el orden natural de las cadenas (método compareTo).
- Declara y construye un mapa ordenado que utilice el comparador. Introduce las entradas
del mapa anterior al nuevo mapa.
- Recorre y muestra por la consola las claves del mapa. Observa que las claves están
ordenadas aplicando el orden del comparador.

Ejercicios colecciones:

Crea un paquete colecciones y añade una clase Utilidades que contenga los siguientes
métodos de clase (static).

1. Implementa un método que tenga como parámetro un argumento de tamaño variable de


tipo String y que retorne un mapa ordenado que asocie cadenas con el número de
repeticiones en el parámetro del método. Por ejemplo, si es llamado con
metodo("hola", "examen", "hola") retornará un mapa ordenado que asociaría
a la cadena “examen” el valor 1 y a la cadena “hola” el valor 2.

2. Escribe un método en Java que tenga como parámetro un mapa que asocie cadenas
con enteros y que devuelva un conjunto con las cadenas que tienen asociado un número
par.

3. La moda es el valor (o valores) de una serie de números que más veces se repite. Por
ejemplo, la moda de la serie {2, 3, 3, 3, 4, 5, 4, 6, 4} es {3, 4}. En el caso de que no se
repita ningún valor la serie no tiene moda. Implementa un método que reciba como
parámetro una serie de números de tamaño variable y devuelva una lista que contenga
la moda de dicha serie.

4. Escribe un método genérico que reciba como parámetro una colección de elementos y
retorne una nueva colección formada por los elementos de la original sin repetidos.

5. Programa un método genérico que acepte como parámetro un mapa que asocie
cadenas con cualquier otro tipo de datos y retorne un conjunto con los valores
almacenados en el mapa

2/8
Java 8 – Caso de estudio

Supongamos que al administrador del sistema de subastas le interesa disponer de una


funcionalidad que le permita ejecutar cualquier tipo de acción (por ejemplo, enviar un correo
electrónico) sobre todos los usuarios registrados que satisfagan un criterio (por ejemplo,
aquellos que sea su cumpleaños).
Vamos a analizar diferentes alternativas para implementar esta funcionalidad. Entre estas
alternativas encontraremos primero soluciones “Java 7” y posteriormente la aplicación de las
nuevas características de Java 8.

Enfoque 1: Definir métodos de búsqueda

El enfoque más simple es implementar en una clase de utilidades (clase Utils) tantos
métodos diferentes como criterios de búsqueda y acciones queramos aplicar sobre los
usuarios registrados. Por ejemplo,

public class Utils {


public static void imprimirUsuariosMayoresDe(List<Usuario> usuarios, int edad){
for (Usuario usuario : usuarios){

if (usuario.getEdad() >= edad) CRITERIO

System.out.println(usuario); ACCIÓN
}
}

public static void imprimirUsuariosEnRangoEdad(List<Usuario> usuarios,


int edadMin, int edadMax){
for (Usuario usuario : usuarios){
if (usuario.getEdad() >= edadMin && usuario.getEdad() <= edadMax)
System.out.println(usuario);
}
}
}

No obstante, este enfoque no es un buen diseño dado que implementamos métodos


prácticamente iguales y las condiciones de búsqueda pueden ser muy amplias y pueden ir
cambiando.

Enfoque 2: Definir los criterios de búsqueda en una clase separada

Una mejora al código anterior consiste en definir cada criterio de búsqueda en una clase
separada. De esta manera, podemos definir un método que imprima los usuarios que
cumplan el criterio que se establece como parámetro:

public static void imprimirUsuarios(List<Usuario> usuarios, Criterio criterio){


for (Usuario usuario : usuarios)
if (criterio.test(usuario))
System.out.println(usuario);
}

3/8
El método imprimirUsuarios es un ejemplo de aplicación del patrón estrategia. El criterio
de búsqueda (estrategia) se define en una interfaz que incluye el método test. Este método
recibe como parámetro un objeto de tipo Usuario y devuelve un valor boolean que indica
si el usuario cumple o no el criterio de búsqueda.

public interface Criterio {

boolean test(Usuario usuario);

De esta forma, tendremos que implementar tantas clases como criterios de búsqueda
necesitemos. Por ejemplo, el criterio que seleccione los usuarios jóvenes, esto es mayores
de edad y menores de 35 años.

public class UsuariosJovenes implements Criterio {

@Override
public boolean test(Usuario usuario) {

return usuario.getEdad() >= 18 && usuario.getEdad() < 35;


}
}

Para imprimir los usuarios jóvenes, tendremos que pasar como parámetro al método
imprimirUsuarios un objeto de la clase que implementa el criterio de búsqueda (o
selección) de usuarios:
public class Programa {

public static void main(String[] args) {


LinkedList<Usuario> usuarios = new LinkedList<Usuario>();

... //Se omite la creación de Usuarios e inserción en la lista

Utils.imprimirUsuarios(usuarios, new UsuariosJovenes());


}
}

Enfoque 3: Utilizar clases anónimas para definir los criterios

El enfoque anterior tiene el inconveniente de que se implementan muchas clases, y algunas


de ellas sólo se van a utilizar en un punto del código. En lugar de crear una clase por cada
criterio, se pueden utilizar clases anónimas para especificar el criterio en el momento de
hacer la llamada al método imprimirUsuarios:

Utils.imprimirUsuarios(usuarios, new Criterio(){


public boolean test(Usuario usuario){
return usuario.getEdad() >= 18 && usuario.getEdad() < 35;
}
});

4/8
Enfoque 4: Utilizar expresiones lambda para especificar el criterio de búsqueda
(Java 8)

Dado que la interfaz Criterio que hemos definido contiene un solo método (método
test), es un ejemplo de interfaz funcional, que se introduce en Java 8. Una característica
útil de este tipo de interfaz es que cuando se implementa se puede omitir el nombre del
método y el tipo de la interfaz. Así, en lugar de utilizar una clase anónima, cuando se llama
al método imprimirUsuarios, se puede pasar como parámetro una expresión lambda
que especifica el comportamiento del método test de la interfaz.

Utils.imprimirUsuarios(usuarios,
usuario -> usuario.getEdad() >= 18 && usuario.getEdad() < 35);

Enfoque 5: Utilizar las interfaces funcionales predefinidas (interfaz Predicate)

La librería de Java proporciona varias interfaces funcionales de utilidad en el paquete


java.util.function, como por ejemplo, la interfaz Predicate<T>.

public interface Predicate<T> {

boolean test(T elem);

La definición de esta interfaz coincide con la definición de la interfaz Criterio que hemos
implementado previamente (Enfoque 2), con la diferencia de que el predicado se define de
forma genérica. Puesto que el lenguaje proporciona ya este tipo de datos, no merece la
pena implementar un tipo tan sencillo en nuestra aplicación y podemos reutilizar el que nos
ofrece la librería de Java. Así, la versión del método imprimirUsuario quedaría como
sigue:

public static void imprimirUsuariosPredicado(List<Usuario> usuarios,


Predicate<Usuario> criterio){
for (Usuario usuario : usuarios)
if (criterio.test(usuario))
System.out.println(usuario);
}

La forma de invocar a esta versión de imprimirUsuarios es igual a la utilizada en el


enfoque 4, en el que hacíamos uso de las expresiones lambda.

Utils.imprimirUsuariosPredicado(usuarios,
usuario -> usuario.getEdad() >= 18 && usuario.getEdad() < 35);

Enfoque 6: Utilizar expresiones lambda para especificar la acción (interfaz Consumer)

La funcionalidad planteada en el caso de estudio requería que se pudiera aplicar cualquier


acción sobre los usuarios que cumplieran un determinado criterio. En el código
implementado hasta el momento, se puede especificar el criterio de selección de los

5/8
usuarios, pero la acción a realizar es fija, imprimir el usuario. Podemos generalizar el
método de manera que la acción que se va a aplicar, sobre los usuarios que cumplen el
criterio de selección, también pueda ser establecida en el método.

Para especificar la acción a realizar sobre los objetos seleccionados se puede utilizar otra de
las interfaces funcionales predefinidas, la interfaz java.util.function.Consumer<T>.
Esta interfaz contiene el método void accept(T t), que acepta un objeto y aplica alguna
acción sobre él, sin retornar nada, por ejemplo, imprimir en la consola. Utilizando esta
interfaz el método imprimirUsuarios se puede generalizar como sigue:

public static void procesarUsuarios(List<Usuario> usuarios,


Predicate<Usuario> criterio,
Consumer<Usuario> accion){

for (Usuario usuario : usuarios){


if (criterio.test(usuario))
accion.accept(usuario);
}
}

La forma de invocar a este método, utilizando expresiones lambda, para imprimir todos los
usuarios jóvenes registrados sería:

Utils.procesarUsuarios(usuarios,
usuario -> usuario.getEdad() >= 18 && usuario.getEdad() < 35,
usuario -> System.out.println(usuario));

De forma alternativa, cuando el parámetro es una interfaz funcional, en lugar de una


expresión lambda, también es posible pasar como parámetro una referencia a un método.
Así, la llamada también podría hacerse de la siguiente forma:

Utils.procesarUsuarios(usuarios,
usuario -> usuario.getEdad() >= 18 && usuario.getEdad() < 35,
System.out::println);

La invocación al método procesarUsuarios realiza las siguientes acciones:


1) Obtiene un usuario de la colección de usuarios.
2) Comprueba si cumple la condición establecida en el predicado (Predicate).
3) Ejecuta una acción sobre el objeto que cumple el predicado (Consumer), en este
caso mostrarlo en la consola.

Enfoque 7: Utilizar streams para procesar los objetos de una colección

En lugar de definir un método que se encargue de procesar los objetos de la colección


utilizando un iterador, se puede utilizar un stream (java.util.Stream) de la colección
(secuencia de objetos que contiene) y las operaciones que proporciona. Estas operaciones
incluyen, entre otras, las funcionalidades para filtrar objetos (filter) y para aplicar una
misma acción sobre todos los objetos de la secuencia (forEach). Así, no necesitaríamos la
clase Utils y la funcionalidad para imprimir todos los usuarios jóvenes quedaría como
sigue:

6/8
usuarios.stream()
.filter(usuario -> usuario.getEdad() >= 18 && usuario.getEdad() < 35)
.forEach(usuario -> System.out.println (usuario));

Los streams también proporcionan, entre otras, una operación para obtener un nuevo
stream con el resultado de la correspondencia del objeto original con otro (map). Así, la
funcionalidad para imprimir el email de todos los usuarios jóvenes sería:

usuarios.stream()
.filter(usuario -> usuario.getEdad() >= 18 && usuario.getEdad() < 35)
.map(usuario -> usuario.getEmail())
.forEach(email -> System.out.println (email));

Ejercicios sobre streams

NOTA: En la sección anterior no se utilizan todas las operaciones soportadas por los
streams (filter, sorted, map, forEach, anyMatch, noMatch, allMatch, count). Revisa las
transparencias del Tema 6 de teoría para completar la información que aquí aparece y poder
implementar los ejercicios propuestos.

Crea la clase PruebaStreams en el paquete pruebas con un método main en el que


implementaremos la siguiente funcionalidad utilizando los streams y las expresiones lambda.
Previo:
• Copia en el método main de la clase PruebaStreams, el código del programa de la
clase PruebaSubasta hasta la creación, e inserción de elementos, en la lista de
usuarios. Crea e inserta las subastas en otra colección.

1) Cuenta los usuarios que tienen cuenta de correo en Gmail. Muestra el resultado en la
consola.

2) Mostrar por la consola los nombres de usuarios que sean propietarios de subastas
ordenados por orden alfabético inverso.

3) Mostrar por la consola los nombres de los productos cuyas subastas hayan recibido
alguna puja ordenados alfabéticamente.

4) Mostrar por la consola el nombre de los productos de aquellas subastas que hayan
recibido pujas superiores a 50 euros.

5) Consultar si hay usuarios que hayan ganado alguna subasta y que sean propietarios de
subastas.

6) Crea un conjunto vacío de pujas, añade a ese conjunto todas las pujas que se hayan
realizado en las subastas.

7/8
Ejercicios de exámenes anteriores relacionados con Java 8

(Febrero 2015) Utilizando el modelo de procesamiento basado en streams, y supuesta una


variable List<Integer> datos; muestra por la consola el número de elementos de la
colección que estén comprendidos entre 0 y 10, ambos inclusive.

(Junio 2015) Declara un método que pueda aceptar como parámetro un constructor sin
argumentos de cualquier colección de cadenas. El método retornará una colección obtenida
a partir del constructor con un solo elemento, la cadena vacía. Escribe un ejemplo que
muestre el uso de este método.

(Julio 2015) Ejercicios:


a) Define una variable de tipo predicado que evalúe si un entero es par.
b) Utilizando el modelo de procesamiento basado en streams, y el predicado anterior, obtén
una lista que contenga sólo los números pares, ordenados de menor a mayor, de una lista
de enteros.

8/8

Você também pode gostar