Capitulo_9_-_colecciones Programacion Java

30
5. Colecciones 5.1 Introducción En un programa que utilice los conceptos de orientación a objetos, lo más común es crear muchos objetos de diferentes clases, y luego realizar operaciones con ellos. Para un programa extremadamente sencillo se pueden tener variables que referencien cada uno de los objetos creados, pero a medida que aumenta la cantidad de objetos que se necesitan, es muy difícil mantener esta forma de referenciarlos. De ahí nace la necesidad de contar con elementos que permitan agrupar los objetos de alguna forma, y faciliten su creación, búsqueda, e incluso su eliminación. La primera forma de agrupar los objetos es utilizar un arreglo 1 . Los arreglos son estructuras que permiten tener elementos de un mismo tipo, y que son referenciados por una variable; en ellos se puede tener acceso a un elemento específico a través de un índice. Una de las grandes desventajas de los arreglos, además del hecho de que no puede modificarse su tamaño una vez se ha definido, es que todo el procesamiento adicional que se requiere para administrar adecuadamente los elementos que contiene, depende del programador. Por ejemplo, si se desea que los elementos del arreglo no se repitan se debe recorrer todo el arreglo cada vez que se vaya a insertar un nuevo objeto para hacer esta verificación, o si se desea ordenar los elementos, hay que idear un mecanismo para lograrlo. Lo ideal, entonces, es contar con estructuras para agrupar objetos, pero que además ofrezcan otros servicios al programador, como la posibilidad de ordenar los elementos automáticamente o de verificar si ya existe el elemento que desea insertar. Java ofrece estas estructuras en lo que se conoce como el Marco de trabajo de Colecciones, o Collection Framework, que se presentará en este capítulo. 5.2 Definición Una colección, o como a veces se le llama: contenedora, es un objeto que está en capacidad de contener referencias individuales a muchos objetos. Las colecciones permiten, básicamente, organizar de alguna manera los objetos que se usarán en el programa, además de ofrecer algunos servicios adicionales para facilitar el manejo de estos objetos. Existen diferentes tipos de colecciones que permiten ofrecer diversos servicios, por ejemplo, algunas ordenan los objetos que contienen, pero pueden ser lentas al insertarlos, mientras que otras insertan los objetos muy rápidamente, pero no de manera organizada. En el paquete java.util de Java se han definido varias clases e interfaces para el manejo de colecciones. Estas clases e interfaces incluyen colecciones que se pueden usar 1 Debe conocerse el concepto de arreglo y su manejo en Java.

description

Se explica de forma detallada el tema de las colecciones en java.

Transcript of Capitulo_9_-_colecciones Programacion Java

Page 1: Capitulo_9_-_colecciones Programacion Java

5. Colecciones 5.1 Introducción En un programa que utilice los conceptos de orientación a objetos, lo más común es crear muchos objetos de diferentes clases, y luego realizar operaciones con ellos. Para un programa extremadamente sencillo se pueden tener variables que referencien cada uno de los objetos creados, pero a medida que aumenta la cantidad de objetos que se necesitan, es muy difícil mantener esta forma de referenciarlos. De ahí nace la necesidad de contar con elementos que permitan agrupar los objetos de alguna forma, y faciliten su creación, búsqueda, e incluso su eliminación. La primera forma de agrupar los objetos es utilizar un arreglo1. Los arreglos son estructuras que permiten tener elementos de un mismo tipo, y que son referenciados por una variable; en ellos se puede tener acceso a un elemento específico a través de un índice. Una de las grandes desventajas de los arreglos, además del hecho de que no puede modificarse su tamaño una vez se ha definido, es que todo el procesamiento adicional que se requiere para administrar adecuadamente los elementos que contiene, depende del programador. Por ejemplo, si se desea que los elementos del arreglo no se repitan se debe recorrer todo el arreglo cada vez que se vaya a insertar un nuevo objeto para hacer esta verificación, o si se desea ordenar los elementos, hay que idear un mecanismo para lograrlo. Lo ideal, entonces, es contar con estructuras para agrupar objetos, pero que además ofrezcan otros servicios al programador, como la posibilidad de ordenar los elementos automáticamente o de verificar si ya existe el elemento que desea insertar. Java ofrece estas estructuras en lo que se conoce como el Marco de trabajo de Colecciones, o Collection Framework, que se presentará en este capítulo. 5.2 Definición Una colección, o como a veces se le llama: contenedora, es un objeto que está en capacidad de contener referencias individuales a muchos objetos. Las colecciones permiten, básicamente, organizar de alguna manera los objetos que se usarán en el programa, además de ofrecer algunos servicios adicionales para facilitar el manejo de estos objetos. Existen diferentes tipos de colecciones que permiten ofrecer diversos servicios, por ejemplo, algunas ordenan los objetos que contienen, pero pueden ser lentas al insertarlos, mientras que otras insertan los objetos muy rápidamente, pero no de manera organizada. En el paquete java.util de Java se han definido varias clases e interfaces para el manejo de colecciones. Estas clases e interfaces incluyen colecciones que se pueden usar

1 Debe conocerse el concepto de arreglo y su manejo en Java.

Page 2: Capitulo_9_-_colecciones Programacion Java

directamente en los programas, pero también permiten la creación de colecciones propias, si no hay ninguna colección que se ajuste a lo requerido por el programador. El marco de trabajo para las colecciones incluye: - Un conjunto de interfaces que definen los métodos básicos que deben tener las

colecciones. - Clases abstractas que implementan los métodos básicos definidos por las interfaces,

pero incluyendo algunos servicios adicionales. Cuando se desea hacer una colección propia puede elaborarse una clase que herede de estas clases abstractas, en lugar de implementar directamente la interfaz o interfaces necesarias.

- Clases concretas, es decir, colecciones listas para usar en los programas. - Otros elementos (interfaces, clases y métodos) que proporcionan servicios

adicionales. La Figura 9 muestra las principales interfaces del marco de trabajo de colecciones.

Figura 1 Interfaces del marco de trabajo de colecciones

5.3 Jerarquía Collection La interfaz Collection define la forma general como se comportan todas las colecciones que de ella se derivan y que hacen mucho más fácil la elaboración de los programas, pues sólo debe conocerse el encabezado del método para utilizarlo, aunque internamente cada clase de la jerarquía implemente el servicio de manera diferente. En esta interfaz se definen los métodos básicos de toda colección, entre los cuales se encuentran: • boolean add(Object): Permite adicionar un objeto a la colección. Observe que se

recibe un parámetro de tipo Object, es decir, cualquier objeto puede guardarse en una colección, pero no tipos básicos. Este método retorna true cuando el objeto se pudo adicionar exitosamente a la colección.

<< interfaz >> Collection

<< interfaz >> List

<< interfaz >> Set

<< interfaz >> SortedSet

<< interfaz >> Map

<< interfaz >> SortedMap

<< interfaz >> Iterator

<< interfaz >> ListIterator

Page 3: Capitulo_9_-_colecciones Programacion Java

• boolean remove(Object): Permite retirar un objeto de la colección, y retorna true si se pudo retirar. Si un objeto se encuentra varias veces en la colección, éste método retira la primera referencia a él.

• boolean contains(Object): Determina si el objeto que se recibe como parámetro se encuentra o no en la colección.

• int size(): Indica cuántos objetos hay actualmente en la colección. • boolean isEmpty(): Retorna true si no hay objetos en la colección. • Iterator iterator(): Retorna un objeto iterador asociado a la colección. Este objeto se

explicará posteriormente. • Object[] toArray(): Realiza una copia de los objetos de la colección y retorna dichas

copias organizadas en un arreglo. No todas las colecciones de la jerarquía permiten realizar todas las operaciones definidas en la interfaz Collection (o en Map, como se verá más adelante). Si alguna operación no es permitida se lanza una java.lang.UnsupportedOperationException. Preguntas • ¿Para qué sirven los métodos addAll y removeAll de la interfaz Collection? • ¿Existe algún método que permita retirar todos los elementos de una colección? Como se puede observar, no es necesario que el programador realice operaciones adicionales para adicionar o retirar un elemento de la colección, como se requería con los arreglos. Basta con invocar el método add o remove de la colección para lograr este propósito. Sin embargo, no existen métodos para “recorrer” la colección, o para obtener una referencia a uno de los objetos que contiene, sin retirarlo de la lista. Esto se debe a que cada clase tiene formas diferentes de obtener los diferentes elementos que la conforman. Para estandarizar la forma de recorrer las colecciones se define la interfaz Iterator que se revisará en detalle más adelante. 5.3.1 Colecciones tipo lista Las listas son agrupaciones de objetos que cuentan con las siguientes características: - Los elementos se encuentran organizados uno después de otro (en secuencia). - Es posible conocer en cualquier momento la posición donde está ubicado un elemento

dentro de la lista. - Permite elementos repetidos. - No se requiere que los elementos almacenados se encuentren ordenados por algún

criterio particular. Dado que una lista permite elementos repetidos, no se realiza ninguna verificación al momento de adicionar un objeto. Una lista, por lo tanto, es útil cuando no hay necesidad de identificar de manera única los elementos.

Page 4: Capitulo_9_-_colecciones Programacion Java

Para ofrecer el comportamiento de las colecciones tipo lista, el framework de colecciones cuenta con interfaces y clases que definen este comportamiento, todas ellas derivadas de la interfaz Collection. 5.3.1.1 Interfaz List Esta interfaz define un comportamiento que permite tener control sobre la posición donde un elemento es insertado en la lista, permite el acceso a cada elemento a través del índice de su posición y realizar búsquedas de elementos sobre la lista. Adicionalmente provee un iterador especial de tipo ListIterator para hacer el recorrido sobre los elementos de la colección. Este iterador se verá en detalle más adelante.

Figura 10 Métodos definidos en la interfaz List

Como se observa en la figura 10, la interfaz List, además de los métodos que hereda de Collection, define, entre otros, los siguientes métodos: • add(int, Object): Adiciona un objeto en la posición indicada en la lista (desplazando los

elementos posteriores a esta posición, hacia la derecha). • Object remove(int): Retira el objeto que hay en la posición dada, y retorna una

referencia a este. • int indexOf(Object): Busca el objeto en la colección y retorna la posición de su primera

aparición. Si el elemento no está en la colección retorna –1.

Page 5: Capitulo_9_-_colecciones Programacion Java

• Object get(int): Retorna una referencia al objeto que se encuentra en la posición que se da en el parámetro.

• ListIterator listIterator(): Retorna un objeto ListIterator asociado a la lista. Esta interfaz se explica posteriormente.

• ListIterator listIterator(int): Retorna un objeto ListIterator que comienza en la posición indicada.

Puede observarse que la interfaz List define métodos que permiten adicionar, eliminar u obtener una referencia a un objeto a través de su posición. Sin embargo, estos métodos no siempre son eficientes, y en su lugar se recomienda recorrer la lista utilizando un iterador. 5.3.1.2 Clase AbstractList Esta clase, de tipo abstracto, implementa a la interfaz List, y define cierta funcionalidad básica para las clases que hereden de ella, es decir, ya contiene el código para algunos de los métodos definidos en List. Es especialmente útil cuando se requiere elaborar una colección propia con el comportamiento de una lista, en cuyo caso la colección hereda de AbstractList , evitando el tener que implementar directamente a la interfaz List.

Figura 11 Métodos definidos en la clase AbstractList

Pregunta: ¿Cuáles son los métodos cuyo código ya fue elaborado en la clase AbstracList?

Page 6: Capitulo_9_-_colecciones Programacion Java

5.3.1.3 Clase ArrayList Esta clase, de tipo concreto, hereda de la clase AbstractList. Un ArrayList es muy parecido a un arreglo, pero su tamaño crece dinámicamente, a medida que se necesita. Al igual que los arreglos, un ArrayList no es muy útil para insertar o eliminar elementos en lugares diferentes al final del mismo, pero es muy fácil de recorrer y muy rápido para encontrar un elemento dada su posición. Los constructores de esta clase son: • ArrayList(): Crea un ArrayList vacío. • ArrayList(Collection): Crea un ArrayList que tiene los elementos que están en la

colección dada. • ArrayList(int): Crea un ArrayList con la capacidad inicial que se indica en el parámetro.

Este valor puede cambiar a medida que se adicionan elementos a la lista. Ejemplo: Se tiene un ArrayList donde se almacenan los libros disponibles para la venta en una Librería, y se requiere verificar si, dado el título de un libro, este existe en la librería para entregarlo al cliente y retirarlo de la colección. Código (apartes):

public class Libro{ private String título; private int páginas; private String autor; public Libro(String elTítulo, int lasPáginas, String elAutor){ título=elTítulo; páginas=lasPáginas; autor=elAutor; } public Libro(String elTítulo){ título=elTítulo; } //Define que dos libros son iguales si tienen el mismo título public boolean equals(Object obj){ if (obj instaceof Libro){ Libro nuevo=(Libro) obj; If (título.equals(nuevo.getTítulo()) return true; return false; } return false; } } import java.util.*; public class Librería{ private static ArrayList libros; public static Libro venderLibro(String título){ Libro pedido=new Libro(título); int posición=libros.indexOf(pedido);

Page 7: Capitulo_9_-_colecciones Programacion Java

continuación… if (posición != -1){ Libro vendido=libros.get(posición); libros.remove(vendido); return vendido; } return null; } public static void main (String args[ ]){ …. Libro libro=venderLibro(título); if (libro!=null){ System.out.println(“Libro vendido”); else System.out.println(“El libro no se encuentra disponible”); … } }

5.3.1.4 LinkedList Esta clase, de tipo concreto, hereda de la clase AbstractSequentialList, que implementa a la interfaz List. Las colecciones de tipo LinkedList ocupan espacios diferentes de la memoria por cada objeto que allí se almacena, de manera que cada elemento “encadena” a su sucesor y antecesor para mantener la lista. Esta forma de implementación hace que el adicionar o remover elementos de cualquier parte de la lista sea muy fácil, sin embargo, dificulta el acceso a un elemento dada su posición, pues requiere recorrer la cadena de enlaces entre sus elementos. Además de los métodos definidos en la interfaz List, una LinkedList define: • addFirst(Object): Adiciona un objeto al comienzo de la lista. • addLast(Object): Adiciona un objeto al final de la lista. • Object removeFirst(): Retira el primer elemento de la lista, y retorna una referencia a

este. • Object removeLast(): Retira el último elemento de la lista, y retorna su referencia. Ejemplo: En un banco se requiere que cada vez que llegue un nuevo cliente a la caja, éste sea agregado al final de la fila de clientes, y cuando alguien deba pasar a la caja para ser atendido, se retire del principio de la fila. Código (apartes):

Page 8: Capitulo_9_-_colecciones Programacion Java

public class Cliente{ private String identificación; private String nombre; public Cliente(String id, String name){ identificación=id; nombre=name; } …. } import java.util.*; public class Banco{ private LinkedList fila; public Cliente pasarACaja(){ Cliente atendido= (Cliente) fila.removeFirst(); return atendido; } public void llegarAFila(Cliente){ fila.addLast(Cliente); } …. }

5.3.2 Colecciones tipo conjunto Los conjuntos son agrupaciones de objetos donde no se pueden tener elementos duplicados. Este tipo de colección es útil cuando los elementos que se desean agrupar deben identificarse de manera única, por ejemplo, el conjunto de estudiantes de una universidad. A continuación se revisarán aquellas interfaces y clases que definen a las colecciones tipo conjunto. 5.3.2.1 Interfaz Set Esta interfaz no define métodos adicionales a los ya definidos en Collection, pero sí determina un comportamiento distinto para algunos de sus métodos. Por ejemplo, aquellas clases que implementen la interfaz Set, deben elaborar el método add de manera que se indique en el retorno si el elemento pudo ser agregado (true) o no (false) a la colección, validando la existencia del mismo en el grupo de elementos. Cabe anotar que para los datos de tipo referenciado (objetos) la igualdad se determina de acuerdo lo establecido en el método equals. Si se requiere determinar la igualdad bajo un criterio distinto al establecido en la clase Object (dos objetos son iguales si ocupan el mismo espacio de memoria), entonces es necesario sobrescribir dicho método en la clase que se quiere comparar.

Page 9: Capitulo_9_-_colecciones Programacion Java

Adicional al método equals, la clase Object define el método hashCode, el cual retorna un valor entero diferente para cada objeto existente en memoria. Se recomienda que al sobrescribir el método equals, también se sobrescriba el método hashCode, de manera que para dos objetos que se definen como iguales, el valor entero retornado por dicho método sea el mismo para ambos objetos, independiente de que los dos ocupen espacios diferentes en la memoria.

Ejemplo: Se desea tener un conjunto de estudiantes, ¿cómo se determina que un estudiante ya existe, y por lo tanto no se adiciona de nuevo a la colección? Puede definirse que la forma de identificar de manera única a un estudiante es a través de un código, por lo que la definición de la clase Estudiante quedaría así:

class Estudiante { private String código; private String nombre; … public boolean equals(Object objeto) { if (objeto instanceof Estudiante){ Estudiante otroEstudiante = (Estudiante)objeto; return (código.equals(otroEstudiante.código)); } return false; } public int hashCode() { return código.hashCode(); } public Estudiante(String código, String nombre) { this.código = código; this.nombre = nombre; } public Estudiante(String código){ this.código=código; nombre=null; } public String toString() { return “[”+código + “-” +nombre + “]”; } }

De esta forma, si se intenta adicionar un estudiante a un conjunto y ya hay un estudiante con ese código, al validar la igualdad con el método equals retornará true, y a su vez, el método add no lo adicionará en el conjunto, retornando false.

La clase String contiene el método equals sobrescrito indicando que dos objetos de este tipo son iguales si la cadena que contienen es la misma. Adicionalmente, en la clase se ha sobrescrito el método hashCode para que el valor retornado sea el mismo para dos cadenas iguales.

Page 10: Capitulo_9_-_colecciones Programacion Java

Pregunta: ¿Para qué se define el segundo constructor de Estudiante únicamente con el atributo código? 5.3.2.2 Interfaz SortedSet Esta interfaz define el comportamiento del conjunto como un grupo de elementos ordenado de manera ascendente, por lo tanto, agrega métodos que deben dar acceso a los elementos de acuerdo al orden en que se encuentran. Algunos de los métodos que define esta interfaz son: • Object first(): Retorna el primer elemento del conjunto, es decir, el menor de todos. • Object last(): Retorna el último elemento (el mayor) del conjunto. • Comparator comparator(): Devuelve el objeto comparator (se explica posteriormente)

que se utiliza para ordernar los elementos. En caso de no contar con un objeto comparator retorna null.

Figura 12 Métodos definidos en la interfaz SortedSet

Existen dos formas para determinar cuándo un objeto es mayor que otro, y de esta forma poder ordenarlos en el conjunto. La primera forma es hacer que las clases cuyos objetos se insertarán en el conjunto implementen la interfaz java.lang.Comparable, y la segunda forma es usar un objeto que implemente la interfaz java.util.Comparator. Estas interfaces se explicarán en detalle a continuación. Interfaz Comparable Esta interfaz permite definir lo que se denomina el orden natural (natural order) de los objetos. La interfaz define un único método: • int compareTo(Object): Debe retornar un entero positivo cuando el parámetro es

menor que el objeto actual, un entero negativo cuando el parámetro es mayor que el objeto actual, y cero si son iguales. Se recomienda que este método sea compatible con la definición que se haga del método equals, es decir, si se retorna cero al comparar dos objetos con compareTo, debe retornarse true al compararlos con equals.

Todas las clases Wrapper implementan esta interfaz, al igual que las clases Date y String.

Page 11: Capitulo_9_-_colecciones Programacion Java

Por ejemplo, se desea tener un conjunto ordenado de revistas, donde la mayor es la que tiene mayor número de páginas. Defina la clase Revista de manera que se permita el ordenamiento de los objetos de este tipo al momento de ingresarlos a un conjunto ordenado. Solución: De acuerdo al enunciado se requiere: - Definir la clase Revista, que debe implementar la interfaz Comparable. - Sobreescribir el método compareTo para establecer el orden de las revistas. - Sobreescribir los métodos equals y hashCode, de manera que sean compatibles con

la definición dada en compareTo.

class Revista implements Comparable { private String nombre; private int páginas; public Revista(String nombre, int páginas) { this.nombre = nombre; this.páginas = páginas; } public int compareTo(Object objeto) { Revista otraRevista = (Revista)objeto; return (páginas - otraRevista.páginas); } // Define que dos revistas son iguales si tienen el mismo número de páginas public boolean equals(Object objeto) { Revista otraRevista = (Revista)objeto; return (páginas == otraRevista.páginas); } public int hashCode() { return páginas; } public String toString() { return (nombre+"-"+páginas); } }

Con la definición de Revista dada, se podrían agregar objetos de este tipo a una colección de tipo SortedSet, quedando ordenadas de menor a mayor cantidad de páginas. Interfaz Comparator Además del orden natural de los objetos, es posible definir también otro tipo de ordenamiento llamado orden parcial (partial orden). Esta forma de definir el orden de los objetos se utiliza cuando hay clases que no implementan la interfaz Comparable, o ya tienen un orden natural definido, pero se desea crear otra colección que los ordene de manera diferente.

Page 12: Capitulo_9_-_colecciones Programacion Java

Para definir el orden parcial es necesario tener una clase que implemente la interfaz Comparator, y luego usar alguno de sus objetos, que se denomina objeto comparador, como un auxiliar para comparar dos objetos (de otra clase), que se desean ordenar. La interfaz Comparator define el siguiente método: • compare(Object, Object): Retorna un entero negativo si el primer objeto es menor que

el segundo, un entero positivo si el primer objeto es mayor que el segundo, y cero si son iguales.

Ejemplo: Modifique el ejemplo anterior, de manera que las revistas no se ordenen por el número de páginas, sino por su nombre. Solución: Teniendo en cuenta que no se quiere modificar el orden natural por el cual se determina si una revista es mayor que otra (cantidad de páginas), se creará un objeto de tipo Comparator que defina el orden parcial solicitado, con base en el orden alfabético del nombre de las revistas:

import java.util.*; class ComparaRevista implements Comparator { public int compare(Object objeto1, Object objeto2) { // La clase Revista debe tener un método getNombre String nombreRevista1 = ((Revista)objeto1).getNombre(); String nombreRevista2 = ((Revista)objeto2).getNombre(); //Se usa el método compareTo de String para determinar cuál de las dos //cadenas del nombre es mayor return nombreRevista1.compareTo(nombreRevista2); } }

Al instanciar la clase ComparaRevista, se obtiene un objeto comparador que puede ser utilizado para agregar objetos de tipo Revista a una colección de tipo SortedSet, quedando ordenadas de menor a mayor según su nombre. Por defecto las colecciones que implementan SortedSet usan el orden natural de los objetos (usando el método compareTo() del objeto a agregar) para agregar elementos de forma ordenada, a no ser que explícitamente se le pida a la colección que utilice el orden parcial (usando el método compare() del objeto Comparator) para hacer el ordenamiento. Para usar el orden parcial, lo normal es que la colección que implementa la interfaz SortedSet tenga un constructor que recibe un parámetro de tipo Comparator. Si se instancia la colección usando este constructor, entonces se usará el orden que define el comparador para ordenar los objetos, pero si no se usa este constructor, entonces se usará el orden natural de los objetos.

Page 13: Capitulo_9_-_colecciones Programacion Java

5.3.2.3 Clase HashSet Esta clase, de tipo concreto, hereda de la clase AbstractSet, que a su vez implementa la interfaz Set. De acuerdo a esto, la clase HashSet define una colección que permite almacenar elementos únicos, pero además, utiliza el código hash de cada objeto a almacenar –obtenido a partir del método hashCode()–, para “ubicarlos” en una tabla especial, denominada tabla hash. Esta forma de almacenar los objetos permite que se realicen búsquedas muy rápidas, ya que el código hash se traduce a una posición en la tabla para llegar directamente al elemento. Dado que los elementos se almacenan de acuerdo a su código hash, no se encuentran ordenados dentro de la colección.

Figura 13 Métodos definidos en la clase HashSet

Además del constructor por defecto, esta clase tiene los siguientes constructores: • HashSet(Collection): Crea un HashSet que contiene los elementos que hay en la

colección dada. • HashSet(int): Crea un HashSet con la capacidad inicial indicada. Esta capacidad inicial

no corresponde al número de elementos sino al número de entradas en la tabla hash,

Si se utiliza un comparador para ordenar los objetos en un SortedSet, es recomendable que el método equals de estos objetos sea compatible con la forma de ordernar de dicho comparador.

Page 14: Capitulo_9_-_colecciones Programacion Java

que es la estructura que facilita la adición y búsqueda de elementos mediante el código hash.

• HashSet(int, float): Construye un HashSet con una tabla hash de la capacidad inicial indicada, y que incrementa su tamaño cuando se encuentra un porcentaje mayor o igual al que se recibe en el segundo parámetro.

Ejemplo: Elabore una aplicación que permita almacenar estudiantes, de acuerdo a la clase Estudiante definida en el apartado 5.3.2.1. La aplicación debe permitir verificar la existencia de un estudiante indicando su código, y si existe, eliminarlo de la colección.

import java.util.*; public class Universidad { private static HashSet conjuntoEstudiantes = new HashSet( ); public static void adicionarEstudiante(String código, String nombre) { Estudiante estudiante = new Estudiante(código,nombre); conjuntoEstudiantes.add(estudiante); } public static boolean eliminarEstudiante(string código){ Estudiante temporal=new Estudiante(código); if (conjuntoEstudiantes.contains(temporal)){ return conjuntoEstudiantes.remove(temporal); } return false; } public static void main(String args[]) { adicionarEstudiante("01","Vincent"); adicionarEstudiante("02", "Marie"); adicionarEstudiante("01","Albert"); System.out.println(eliminarEstudiante(“02”); System.out.println(elminarEstudiante(“05”); } }

Al terminar de adicionar al tercer estudiante, quedaría en la colección

[02-Marie] [01-Vincent]

Como se puede observar, no se adicionó el último estudiante, pues ya existía otro con el mismo código en el conjunto. También se puede notar que, a diferencia de las listas, los elementos no tienen una posición predeterminada en el conjunto, ni están almacenados en forma ordenada. El resultado de las dos eliminaciones sería: true false

Page 15: Capitulo_9_-_colecciones Programacion Java

5.3.2.4 Clase TreeSet Esta clase, de tipo concreto, también hereda de la clase AbstractSet, pero además implementa la interfaz SortedSet, lo cual le permite ser una colección ordenada. El nombre de la clase se debe a la estructura de tipo árbol que utiliza internamente para mantener ordenados los elementos. La explicación de esta estructura está más allá del alcance de este libro, pero se puede indicar que su utilización hace que sean rápidos los tiempos de búsqueda de los objetos dentro de la colección.

Figura 14 Métodos definidos en la clase TreeSet

Esta clase tiene varios constructores además del constructor por defecto, entre los que se encuentran:

• TreeSet(Comparator): Crea un TreeSet que ordena los elementos con ayuda del objeto comparador que recibe como parámetro. Esto permite modificar la forma de ordenamiento establecida por defecto (dada por el orden natural de los elementos).

• TreeSet(Collection): Recibe por parámetro una colección de cualquier tipo (de la jerarquía de Collection) y retorna un TreeSet con los elementos ordenados de acuerdo a su orden natural.

Page 16: Capitulo_9_-_colecciones Programacion Java

Ejemplo: Elabore una aplicación que permita almacenar las revistas, de acuerdo a la definición de la clase Revista dada en el apartado 5.3.2.2, de manera que no se almacenen revistas repetidas (con igual cantidad de páginas), y que queden ordenadas de acuerdo a su cantidad de páginas, de menor a mayor.

import java.util.*; public class Revistero { public static void main(String args[]) { TreeSet revistasOrdenadas = new TreeSet(); revistasOrdenadas.add(new Revista("Web",50)); revistasOrdenadas.add(new Revista("Caricaturas",40)); revistasOrdenadas.add(new Revista("Inventos",80)); revistasOrdenadas.add(new Revista("Java",50)); revistasOrdenadas.add(new Revista("Literatura",35)); System.out.println(revistasOrdenadas); } }

La salida es:

[Literatura-35, Caricaturas-40, Web-50, Inventos-80] Esta salida nos muestra cómo las revistas quedaron ordenadas por el número de páginas, y además no se tienen dos revistas con el mismo número de páginas. 5.4 Iteradores Los iteradores son clases a partir de las cuales se crean objetos que permiten recorrer una colección. El recorrido involucra que se vaya pasando uno a uno por los elementos que contiene la colección, sin importar de qué tipo es la estructura interna utilizada para definirla (arreglo, lista encadena, árbol, tabla hash, etc). En algunos colecciones, el iterador también puede eliminar un elemento de la colección. A continuación se revisará la interfaz que define a los iteradores. 5.4.1 Interfaz Iterator Esta interfaz define el comportamiento de un objeto iterador, ofreciendo métodos que permiten moverse a través de los objetos, obtener una referencia a cada objeto e incluso eliminarlos.

Page 17: Capitulo_9_-_colecciones Programacion Java

Figura 15 Métodos definidos en la interfaz Iterator

• boolean hasNext(): Retorna true si hay más elementos por recorrer en la colección. • Object next(): Retorna una referencia al siguiente elemento en la colección. Al llegar al

final de la colección puede lanzar una java.util.NoSuchElementException. • remove(): Elimina el último objeto que fue retornado con la invocación al método next.

Si se intenta llamar este método sin haber llamado previamente a next se lanzará una java.lang.IllegalStateException. No todas las colecciones soportan esta operación.

El dibujo ilustra el comportamiento de un objeto iterador al recorrer una colección de tres objetos (Ver Figura 16).

Page 18: Capitulo_9_-_colecciones Programacion Java

Figura 16 Uso de un iterator

uno dos tres Se obtiene el iterador: iterador = colección.iterator();

iterador

uno dos tres Se obtiene una referencia al primer elemento: ref = iterador.next();

iterador

ref

uno dos tres Se obtiene una referencia al segundo elemento: ref = iterador.next();

iterador

ref

uno tres Se retira el segundo el elemento de la colección: iterador.remove();

iterador

ref dos

¡Si se intenta llamar de nuevo a remove() se lanzará una IllegalStateException!

uno tres Se obtiene una referencia al siguiente elemento (tres): ref = iterador.next();

iterador ref

Page 19: Capitulo_9_-_colecciones Programacion Java

Todas aquellas colecciones que desean ofrecer el recorrido de los elementos que contienen usando un iterador, implementan a la interfaz Iterable. En esta interfaz está definido el encabezado del método iterator(), el cual retorna el objeto iterador. Dado que la interfaz Collection implementa a Iterable, todas las interfaces que hereden de ella o las clases que la implementen, deberán contar con el método iterator(). Pregunta: ¿Qué diferencias observa en la forma como se define el método para remover objetos en la interfaz Collections frente a como lo define la interfaz Iterator? Ejemplo: Una constructora tiene varias casas para la venta, que son objetos que se encuentran en una colección. Cuando un cliente desea ver las casas se recorre la colección para mostrarle la información de las mismas, y si desea comprarla se retira de la colección. Diagrama de clases simplificado:

Código (apartes):

public class Casa { private double área; private double precio; private String descripción; public Casa(double área, double precio, String descripción) { this.área = área; this.precio = precio; this.descripción = descripción; } public String toString() { return (descripción + " Area: "+área); } } import java.util.*; public class Constructora { private Collection colecciónCasas = new ArrayList( ); public void adicionarCasa(double área, double precio, String descripción) { colecciónCasas.add(new Casa(área,precio, descripción)); }

Casa

área precio descripción Casa()

Constructora

mostrarCasas() adicionarCasa() retirarCasa()

*

Page 20: Capitulo_9_-_colecciones Programacion Java

public void mostrarCasas() { Iterator iterador = colecciónCasas.iterator(); while (iterador.hasNext()) { Casa casa = (Casa)iterador.next(); System.out.println(casa); String comprar = Console.readLine("¿Desea comprar la casa? (S/N)"); if (comprar.equalsIgnoreCase("s")) { iterador.remove(); } } } }

Pregunta ¿Qué sucede si en lugar de la instrucción iterador.remove(), en el código anterior, se utiliza colecciónCasas.remove(casa)? 5.4.2 Interfaz ListIterator Esta interfaz define el comportamiento de un iterador especial para las colecciones de tipo Lista. El tipo de recorrido ofrecido por un objeto de tipo ListIterator permite recorrer la lista en dos direcciones, hacia adelante o hacia atrás.

Figura 17 Métodos definidos en la interfaz ListIterator

Además de los métodos definidos en Iterator, ListIterator define entre otros:

Debe tenerse cuidado con las operaciones que retiran o adicionan elementos a la lista después de creado el iterador, puesto que los iteradores pueden presentar problemas si se modifica la colección que están recorriendo. En estos casos se lanza una ConcurrentModificationException.

Page 21: Capitulo_9_-_colecciones Programacion Java

• boolean hasPrevious(): Retorna true si hay un elemento antes de la posición actual del iterador.

• Object previous(): Retorna el elemento anterior, si lo hay. • add(Object): Adiciona un objeto en la posición actual del iterador. Si el iterador se

acabó de crear se adicionará el elemento al comienzo de la lista. Después de insertar el elemento una llamada a previous retornará una referencia al mismo.

• set(Object): Reemplaza el último elemento que retornó el iterador (con next o previous), con el objeto que llega como parámetro.

Los últimos dos métodos, que permiten adicionar o modificar un elemento en la colección, resultan más eficientes que usar los métodos de List para los mismos propósitos. Ejemplo: Se tiene una lista de camiones que son cargados con mercancía para llevar a diferentes lugares del país. Cuando llega un nuevo camión, éste se ubica después del primer camión en la lista que ya tenga su carga completa. Diagrama de clases:

Código (apartes):

class Camion { private int capacidad; private boolean cargado; public boolean estáCargado() { return cargado; } public Camion(int cap, boolean carg) { capacidad = cap; cargado = carg; } } import java.util.*; public class Camiones { private List listaCamiones = new LinkedList(); public void ingresarCamión(Camion camiónNuevo) { ListIterator iterador = listaCamiones.listIterator();

Camión

capacidad cargado

Camiones

ingresarCamión()

*

Page 22: Capitulo_9_-_colecciones Programacion Java

while (iterador.hasNext()) { Camion camión = (Camion)iterador.next(); if (camión.estáCargado()) { iterador.add(camiónNuevo); break; } } } }

5.5 Jerarquía Map La interfaz Map define otro “estilo” de colecciones en Java, donde se tienen parejas clave/valor, llamadas normalmente entradas (entry). La idea es que las claves sean únicas, y a través de una clave se puede obtener el valor asociado a ella. Tanto la clave como el valor deben ser objetos, no tipos básicos. Es común identificar a las colecciones de esta jerarquía como de tipo diccionario. En un diccionario se tienen palabras únicas, y a cada palabra está asociado su significado. Haciendo el símil con el diccionario, en una colección tipo map tendríamos una clave – la palabra en el diccionario --, con su valor asociado – el significado de esa palabra --. Aunque las colecciones de esta jerarquía también son grupos de objetos, la forma de trabajar con ellos es un poco diferente, y por lo tanto la interfaz Map no hereda de Collection.

Figura 18 Métodos definidos en la interfaz Map

Page 23: Capitulo_9_-_colecciones Programacion Java

Algunos de los métodos definidos en la interfaz Map son: • Object put(Object, Object): Adiciona una entrada al Map (pareja clave/valor). Si ya

existía en el Map la clave que se está adicionando, entonces simplemente se reemplaza el valor que tenía asociado por el nuevo. Retorna el valor anterior asociado a la clave, o null si no existía previamente en el Map.

• Object remove(Object): Retira del Map la pareja clave/valor que corresponde a la clave dada. Retorna el valor que tenía asociado la clave en el Map.

• boolean containsKey(Object): Permite determinar si la clave que se pasa como parámetro existe en el Map.

• boolean containsValue(Object): Retorna true si el valor recibido en el parámetro existe en el Map al menos una vez (los valores pueden repetirse, a diferencia de las claves).

• boolean isEmpty(): Indica si el Map no tiene ninguna entrada. • Object get(Object): Este es uno de los métodos más útiles de Map, ya que recibe una

clave y retorna el valor asociado, es decir, ¡Elimina las operaciones de búsqueda que se requieren para otras colecciones!

• int size(): Devuelve el tamaño del Map (número de entradas). • Set keySet(): Retorna una vista de las claves como un conjunto. • Collection values(): Retorna una vista de los valores del Map, como una colección. • Set entrySet(): Retorna una vista de las entradas como una colección. Los elementos

de esta colección son de tipo Map.Entry. Al igual que en las colecciones, no todas las implementaciones de Map soportan todas las operaciones definidas en la interfaz, en cuyo caso se lanzará una UnsupportedOperationException. Aunque los Maps no tienen un mecanismo para recorrerlos, como el iterador de las colecciones, sí es posible obtener el conjunto de entradas, el conjunto de claves o la colección de valores, y cada uno de éstos se puede recorrer con iteradores. 5.5.1 Interfaz SortedMap La interfaz SortedMap es similar a la interfaz SortedSet, en el sentido de que permite ordenar los elementos, pero en este caso se ordenan de acuerdo a las claves de manera ascendente. El orden de los elementos en un SortedMap está dado por el orden natural de los objetos clave, o por el orden que determine un objeto comparador que se pase como parámetro a una de las clases que implementen SortedMap.

Page 24: Capitulo_9_-_colecciones Programacion Java

Figura 19 Métodos definidos en la interfaz SortedMap

Algunos de los métodos que define esta interfaz son: • Comparator comparator(): Retorna el objeto comparador usado para ordenar, si lo

hay. • Object firstKey(): Retorna la primera clave del Map, es decir, la menor. • Object lastKey(): Devuelve la última clave del Map. 5.5.2 Interfaz Map.Entry Esta interfaz define métodos que permiten acceder a la información de las parejas clave/valor almacenadas en un Map.

• Object getKey(): Retorna el objeto clave. • Object getValue(): Retorna el objeto valor. • Object setValue(Object): Permite modificar el valor actual de la entrada,

reemplazándolo con el que se recibe como parámetro. Retorna el valor anterior. 5.5.3 Clase AbstractMap Esta clase, de tipo abstracto, implementa a la interfaz Map, y define cierta funcionalidad básica para las clases que hereden de ella. Es especialmente útil cuando se requiere elaborar una colección propia con el comportamiento de un diccionario, en cuyo el

Page 25: Capitulo_9_-_colecciones Programacion Java

diccionario hereda de AbstractMap, evitando el tener que implementar directamente a la interfaz Map.

Figura 20 Métodos definidos en la clase AbstractMap

Pregunta: ¿Cuáles son los métodos cuyo código ya fue elaborado en la clase AbstracMap? 5.5.4 Clase TreeMap Esta clase, hija de AbstractMap, y que implementa la interfaz SortedMap, permite tener parejas clave/valor ordenadas. Al igual que la clase TreeSet, permite ordenamientos dados por el orden natural de los objetos, o dados por un objeto comparador. Este último ordenamiento se obtiene cuando se utiliza el constructor TreeMap(Comparator). Los constructores de esta clase son: • TreeMap(): Constructor por defecto. Almacenará los elementos de acuerdo al orden

natural definido en los objetos clave.

Page 26: Capitulo_9_-_colecciones Programacion Java

• TreeMap(Comparator): Crea un TreeMap en donde los elementos se ordenarán de acuerdo al orden parcial de sus claves, definido en el objeto comparador que se pasa por parámetro

• TreeMap(Map): Crea un TreeMap que contiene los elementos del Map que se pasa por parámetro. Los elementos se almacenarán de acuerdo al orden natural de las claves, por lo que la clase que define al objet clave debe implementer la interfaz Comparable.

• TreeMap(SortedMap):Crea un TreeMap que contiene los elementos del SortedMap, siguiendo el mismo criterio de ordenamiento utilizado en el SortedMap.

Ejemplo En este ejemplo se definen dos clases: Canción y Compositor. De ambas clases se crean varios objetos y se adicionan en un TreeMap. La clave para encontrar cada compositor es la canción, de manera que se almacenarán por orden alfabético de acuerdo al título de la canción. Se define adicionalmente un método que permite mostrar las canciones registradas en orden alfabético.

public class Canción implements Comparable{ private String título; private double duración; public Canción(String elTítulo, String laDuración){ título=elTítulo; duración=laDuración; } public Canción(String elTítulo){ título=elTítulo; } public int compareTo(object obj){ if (obj instanceof Canción){ Canción canción=(Canción) obj; return título.compareTo(canción.getTítulo()); } return 1; } … } public class Compositor{ private String nombre; private String docIdentidad; private String dirección; private String teléfono; continúa

Page 27: Capitulo_9_-_colecciones Programacion Java

continuación public Compositor(String elNombre,String elDoc, String laDirección, String elTel){ nombre=elNombre; docIdentidad=elDoc; dirección=laDirección; teléfono=elTeléfono; } …. } import java.util.*; public class Disquera{ private TreeMap canciones=new TreeMap(); public agregar(Canción canción, Compositor compositor){ canciones.put(canción,compositor); } public void mostrarCanciones(){ Set clavesCanción=canciones.keySet(); Iterator iterador=clavesCanción.iterator(); while (iterador.hasNext()){ Canción canción=(Canción) clavesCanción.next(); System.out.println(“Nombre: “+canción.getNombre()+”\n”); } } … }

5.5.5 Clase HashMap Esta clase, hija de AbstractMap, permite tener parejas clave/valor organizadas usando el código hash del objeto clave, por lo que es importante que los objetos que sean claves redefinan el método hashCode. Cada vez que una pareja se va a adicionar o buscar en el HashMap, el código hash de la clave es convertido a una posición dentro de la tabla, lo cual hace muy eficientes ambas operaciones. Algunos constructores de esta clase son: • HashMap(): Crea un HashMap con una capacidad inicial por defecto de 16. • HashMap(int): Crea un HashMap con la capacidad inicial especificada en el parámetro • HashMap(Map):¸Crea un HashMap a partir del Map recibido por parámetro. El comportamiento de esta clase es similar al que se indicó para HashSet. Debe tenerse en cuenta que las entradas almacenadas en el HashMap no quedan ordenadas. Ejemplo: Se han definido las clases Placa y Vehículo para el sistema de tránsito de la ciudad. Cada placa guarda la información de su identificación, fecha y ciudad de expedición, y de cada vehículo se almacena la información de su modelo, color, marca y propietario. En el

Page 28: Capitulo_9_-_colecciones Programacion Java

siguiente código se tienen parejas placa,vehículo en un HashMap, indicando la relación de la placa asignada a cada vehículo, se obtiene la información de un vehículo a partir de su placa y se recorren las entradas para mostrar la información de la placa y el modelo de cada vehículo registrado.

import java.util.*; public class Placa{ private String identificación; private String ciudad; private Date fecha; public Placa(String id,String laCiudad, Date laFecha){ identificación=id; ciudad=laCiudad; fecha=laFecha; } public Placa(String id){ identificación=id; } public int hashCode(){ return identificación.hashCode(); } public boolean equals(Object obj){ if (obj instanceof Placa){ Placa nueva=(Placa) obj; return identificación.equals(nueva.getPlaca()); } return false; } ….. } public class Vehiculo { private String modelo; private String color; private String marca; private String propietario; public Vehiculo(String marca, String color, String modelo, String propietario){ this.marca = marca; this.color = color; this.modelo = modelo; this.propietario=propietario; } public String getColor() { return color; } … }

Page 29: Capitulo_9_-_colecciones Programacion Java

import java.util.*; public class Tránsito { private HashMap autos = new HashMap(); public Vehiculo buscar(Placa placa){ Vehiculo auto = (Vehiculo)autos.get(placa); return auto; } …. public void mostrarVehiculos(){ Set conjunto = autos.entrySet(); Iterator iterador = conjunto.iterator(); while (iterador.hasNext()) { Map.Entry entrada = (Map.Entry)iterador.next(); Placa placa = (Placa)entrada.getKey(); Vehiculo carro = (Vehiculo)entrada.getValue(); System.out.println(placa.getIdentificación() +"-"+carro.getModelo()); } } }

5.6 Otros elementos En el marco de trabajo de colecciones existe una clase especial llamada Collections. Esta clase contiene únicamente métodos estáticos, que permiten realizar diversas operaciones con las colecciones. Algunos de estos métodos son: • int binarySearch(List, Object): Busca el objeto dado en la lista, la cual debe estar

ordenada de manera ascendente. Retorna la posición donde se encuentra el objeto o –1 si no se encuentra.

• void reverse(List): Invierte el orden de los elementos de la lista. • sort(List): Ordena los elementos de la lista, de acuerdo con su orden natural. • sort(List, Comparator): Ordena los elementos de la lista, de acuerdo con el orden dado

por el comparador. • Object min(Collection): Retorna el menor elemento de la colección, según el orden

natural de los objetos. • Object max(Collection): Retorna el mayor elemento en la colección, de acuerdo con su

orden natural.

Page 30: Capitulo_9_-_colecciones Programacion Java

Ejercicios • Complemente los ejemplos presentados en este capítulo, definiendo para cada uno el

tipo de colección que se puede utilizar. • En una gran biblioteca de la ciudad han decidido facilitar las búsquedas de los

diferentes recursos con los que cuenta, y le han pedido que modele esta situación (utilizando colecciones) y realice el programa en Java, teniendo en cuenta: - Todos los recursos (libros y revistas) tienen un nombre y un número que los

identifica. Los libros tienen, además, el nombre de su autor o autores (pueden ser varios) y la editorial. Las revistas tienen la fecha de publicación y el nombre de todos los artículos que contienen, indicando para cada uno su autor.

- Todos los recursos están organizados en estantes, y es importante saber el número del estante y la posición dentro del mismo que ocupa cada recurso.

- En general la forma de buscar todos los recursos es a través de su nombre, por lo que le han pedido que se pueda realizar esta búsqueda rápidamente.

- Cada vez que se consulta una revista deben desplegarse todos los artículos que tiene.

• Para el ejercicio anterior incluya una nueva opción que permita realizar búsquedas por

autor. • En una empresa con muchos empleados se deben generar reportes periódicos donde

se muestre la información de todos ellos. Estos reportes deben estar ordenados alfabéticamente. Determine el tipo de colección que se puede utilizar en este caso y elabore el programa correspondiente.