Está en la página 1de 9

Collections de Java

Introduccin
Un tema que aparece frecuentemente en la programacin es el de organizar una coleccin de objetos en memoria. Este tema tambin es conocido con el ttulo "estructuras de datos", y se relaciona tambin a distintos algoritmos para agilizar el acceso. Antes de hablar especficamente de las colecciones de Java voy a desviarme un rato hablando un poco sobre el problema en general de almacenar colecciones de datos en memoria.

Algoritmos y estructuras de datos


Todo programador aprende, apenas empieza, un par de estructuras de datos simples. Podramos decir que la primer estructura de datos es la variable. Almacena un solo objeto, y el tiempo de acceso es nfimo. La segunda estructura de datos que siempre se aprende es el arreglo (en ingls "array"). Esta coleccin nos permite obtener un valor dado una posicin en una tabla. Es muy veloz ya que esta estructura tiene un fuerte paralelo con cmo se organiza internamente la memoria de una computadora. Los valores en un arreglo estn en orden, y en ese mismo orden estn dispuestos en la memoria, por lo tanto el acceso lo maneja directamente el hardware (porque la memoria funciona fsicamente como un arreglo). Un arreglo en Java se hace as (pero asumamos que ya se sabe).
String[] nombres = new String[10];

Muchos programadores se detienen aqu. Ayuda a esto el hecho de que en algunos lenguajes el arreglo era la estructura de datos ms compleja soportada (por ejemplo en BASIC... o incluso C!). Uno puede usar arreglos para todo. Quiero guardar la lista de helados que me gusta? Las notas que se sacaron los alumnos de una clase? La coleccin de coordenadas que forman un mapa? Puede guardarse en un arreglo... ahora... supongamos que quiero guardar las notas de los alumnos de una clase. Si quiero usar un arreglo podra actuar as: Creo una clase con el nombre del alumno y su nota:
class Nota{ String alumno; int nota; Nota(String alumno, int nota) { this.alumno = alumno; this.nota = nota; } public int getNota() { return nota; } public String getAlumno() { return alumno; }

Ahora almaceno las notas de 500 alumnos:


Nota[] notas = new Nota[500]; notas[0] notas[1] notas[2] notas[3] // ... notas[499] = new Nota("Elas", 9); = = = = new new new new Nota("Juan", 7); Nota("Pedro", 8); Nota("Manuel", 4); Nota("David", 6);

Ahora... Qu pasa si queremos averiguar la nota de Elas? No sabemos "a priori" en qu posicin del arreglo est guardada la nota. La nica manera que tenemos de proceder es simplemente recorrer el arreglo y preguntar si ya llegamos al alumno que queremos:
for(Nota n : notas) if(n.getNombre().equals("Elas")) return n.getNota();

Esto es lentsimo! Est bien, "Elas" es el peor caso, ya que se encuentra ltimo. A Elas lo encontraremos despus de haber efectuado 499 comprobaciones. Pero si suponemos que todos los alumnos son accedidos ms o menos con una misma probabilidad tenemos que el nmero de comprobaciones promedio que se necesitarn por bsqueda de alumno es de 250! Podemos hacerlo mejor? Qu tal si pudiramos contar con que el orden de las notas coincide con el orden alfabtico del nombre de los alumnos? En ese caso podramos ir al alumno 250, y preguntar: Elas es mayor o menor alfabticamente que vos? Si el alumno 250 es Luis, Elas claramente est antes qu l. Luego, Elas est entre el 0 y el 249. Entonces vamos a comprobar al alumno 125, que es, por ejemplo, "Carlos". Elas est despus que Carlos, entonces sabemos que su posicin es mayor a 125 (y era menor a 249). As vamos acotando hasta llegar a Elas en, a lo sumo, 9 comprobaciones. A este algoritmo se le suele llamar "bsqueda dicotmica". No hace falta entender del todo el prrafo anterior. El quid de la cuestin es entender que, mediante algoritmos, redujimos la necesidad de comprobar 250 veces en promedio, a comprobar a lo sumo nueve veces. Y si en vez de 500 nombres tenemos 100.000 necesitaremos comprobar, a lo sumo, 17 veces. El uso de este algoritmo depende del hecho de que los datos que ordenamos estn ordenados. Asimismo cada una de las soluciones puede traer problemas secundarios que sern ms o menos importantes de acuerdo a nuestras necesidades. En el ejemplo anterior, por ejemplo, necesitamos mantener un arreglo ordenado... qu pasa si se agrega un nuevo alumno? Habr que insertarlo en la posicin correcta de acuerdo al orden alfabtico y desplazar a todos lo que queden a su derecha un lugar. En el peor caso es recorrer todo el arreglo!

Todo esto quiere decir que el tipo de estructura de datos que se use depender de lo que puede asumir de la coleccin almacenada y de cmo se espera que esa coleccin sea usada (cmo sea leda y cmo sea modificada). Algunas preguntas a hacerse son:

Me interesa mantener el orden abitrario original? Puedo (como con las notas) reordenar los tems? Hay duplicados? Con qu frecuencia es necesario aadir nuevos elementos?

Colecciones en Java
Java tiene desde la versin 1.2 todo un juego de clases e interfaces para guardar colecciones de objetos. En l, todas las entidades conceptuales estn representadas por interfaces, y las clases se usan para proveer implementaciones de esas interfaces. Una introduccin conceptual debe entonces enfocarse primero en esas interfaces. La interfaz nos dice qu podemos hacer con un objeto. Un objeto que cumple determinada interfaz es algo con lo que puedo hacer X. La interfaz no es la descripcin entera del objeto, solamente un mnimo de funcionalidad con la que debe cumplir. Como corresponde a un lenguaje tan orientado a objetos, estas clases e interfaces estn estructuradas en una jerarqua. A medida que se va descendiendo a niveles ms especficos aumentan los requerimientos y lo que se le pide a ese objeto que sepa hacer.

Collection
La interfaz ms importante es Collection. Una Collection es todo aquello que se puede recorrer (o iterar) y de lo que se puede saber el tamao. Muchas otras clases extendern Collection imponiendo ms restricciones y dando ms funcionalidades. Es de notar que el requisito de "que se sepa el tamao" hace inconveniente utilizar estas clases con colecciones de objetos de las que no se sepa a priori la cantidad (sto podra considerarse una limitacin de este framework). Por las dudas vuelvo a aclarar: No puedo construir una Collection. No se puede hacer new de una Collection, sino que todas las clases que realmente manejan colecciones son Collection, y admiten sus operaciones. Las operaciones bsicas de una collection entonces son: add(T) Aade un elemento. iterator() Obtiene un iterador que permite recorrer la coleccin visitando cada elemento una vez. size() Obtiene la cantidad de elementos que esta coleccin almacena. contains(t)

Pregunta si el elemento t ya est dentro de la coleccin. iterator() Obtiene un iterador para recorrer la coleccin. Si yo tengo un objeto Collection hay muchas cosas que no puedo asumir. No puedo asumir que el orden en el que lo recorro sea relevante, es decir, que si lo recorro de nuevo vea los elementos en el mismo orden en el que los vi la primera vez. Tampoco puedo asumir que no hay duplicados. No puedo asumir caractersticas de rendimiento: Preguntar si existe un objeto en la coleccin puede tardar desde muy poco hasta... mucho =). Una capacidad de un objeto Collection es la de poder ser recorrido. Como a este nivel no est definido un orden, la nica manera es proveyendo un iterador, mediante el mtodo iterator(). Un iterador es un objeto paseador que nos permite ir obteniendo todos los objetos al ir invocando progresivamente su mtodo next(). Tambin, si la coleccin es modificable, podemos remover un objeto durante el recorrido mediante el mtodo remove() del iterador. El siguiente ejemplo recorre una coleccin de Integer borrando todos los ceros:
void borrarCeros(Collection<Integer> ceros) { Iterator<Integer> it = ceros.iterator(); while(it.hasNext()) { int i = it.next(); if(i == 0) it.remove(); } }

En este ejemplo hago uso de la conversin automtica entre Integer e int. A partir de Java 6 hay una manera simplificada de recorrer una collection (que sirve si no necesitamos borrar elementos). Se hace mediante una nuevo uso del keyword for:
void mostrar(Collection<?> col) { for(Object o : col) System.out.println(o); }

Internamente este cdigo no hace otra cosa que obtener el iterador, pero queda mucho ms elegante y legible de esta manera. Pero al mismo tiempo, el non disponer del iterador explcitamente hace imposible usar esta sintaxis para hacer lo que hice en el ejemplo previo: ir borrando elementos. Hay cuatro interfaces que extienden Collection: List, Set, Map y Queue. Cada una de ellas agrega condiciones sobre los datos que almacena y como contrapartida ofrece ms funcionalidad. A continuacin cuento de qu se trata cada una de ellas.

List
Un List, o simplemente lista, es una Collection. La diferencia que tiene una lista con una Collection es que la lista mantiene un orden arbitrario de los elementos y permite acceder a los elementos por orden. Podramos decir que en una lista, por lo general, el orden es dato. Es decir, el orden es informacin importante que la lista tambin nos est almacenando. No hay ningn mtodo en Collection para obtener el tercer elemento. No lo puede haber porque, como se dijo, a nivel Collection ni siquiera estamos seguros de que si volvemos a recorrer la coleccin los elementos aparecern en el mismo orden. Una lista s debe permitir acceder al tercer elemento, por eso se aaden los siguientes mtodos: get(int i) Obtiene el elemento en la posicin i. set(int i, T t) Pone al elemento t en la posicin i. Existen en Java principalmente dos implementaciones de List, las dos tiles en distintos casos. Una de ellas es casi igual a los arreglos comunes (como el de notas que usaba ms arriba para ejemplificar). Esta implementacin es ArrayList. La ventaja de ArrayList por sobre un array comn es que es expansible, es decir que crece a medida que se le aaden elementos (mienras que el tamao de un array es fijo desde su creacin). El tiempo de acceso a un elemento en particular es nfimo, pero si queremos eliminar un elemento del principio, o del medio, la clase debe mover todos los que le siguen a la posicin anterior, para tapar el agujero que deja el elemento removido. Esto hace que sacar elementos del medio o del principio sea costoso. La otra implementacin es LinkedList (lista enlazada). En sta, los elementos son mantenidos en una serie de nodos atados entre s como eslabones de una cadena. Cada uno de estos nodos apunta a su antecesor y al elemento que le sigue. Nada ms. No hay nada en cada uno de esos nodos que tenga algo que ver con la posicin en la lista. Para obtener el elemento nmero n, esta implemetacin de List necesita entonces empezar desde el comienzo, desde el primer nodo, e ir avanzando al siguiente n veces. Buscar el elemento 400 entonces implica 400 de esos pasitos. La ventaja es que es posible eliminar elementos del principio de la lista y del medio de manera muy eficiente. Para elminar un elemento solamente hay que modificar a sus dos vecinos para que se conecten entre s ignorando al elemento que se est borrando. Como en una cadena, se retira un eslabn abrindo los eslabones adyacentes al que se elimin y cerrndolos de modo que lo excluyan. No es necesario hacerle ningn cambio al resto de los elementos de la lista. En otros lenguajes lidiar con listas enlazadas puede ser un poco ms trabajoso. En Java, LinkedList se usa exactamente igual que otros tipos de List, por lo que no hay que saber nada adicional para empezar a usarla. Bueno, esto no es del todo cierto... hay que tener muy en claro sus particularidades en cuanto a rendimiento. Su mtodo get(int) es particularmente lento porque, como dije, necesita recorrer para llegar al elemento pedido. Esto hace que recorrer la ista con un simple for(int i = 0 ; i < lista.size(); i++) sea tremendamente lento! La complejidad pasa de ser lineal a

cuadrtica, es decir: Si se recorre as una lista de 300 elementos, se tarda como si tuviera 44.850 elementos! Una LinkedList slo debe recorrerse mediante iteradores. Un uso ideal de LinkedList es para la creacin de colas, en las que los elementos se aaden al final, y se eliminal del comienzo. Para este uso se puede usar, en vez de List, la interfaz Queue (tambin implementada por LinkedList) que es ms especfica para esta tarea.

Set
Un Set es una Collection. Set es la palabra inglesa para conjunto y los desarrolladores de Java estaban pensando en lo que matemticamente se conoce como conjunto. Por sobre lo que es una collection, un set agrega una sola restriccin: No puede haber duplicados. Por lo general en un set el orden no es dato. Si bien es posible que existan sets que nos aseguren un orden determinado cuando los recorremos (por ejemplo obtener strings en orden alfabtico), ese orden no es arbitrario y decidido por nosotros, ya que la interfaz Set no tienen ninguna funcionalidad para manipularlo (como si lo admite la interfaz List). La ventaja de utilizar Sets es que preguntar si un elemento ya est contenido mediante contains() suele ser muy eficiente. Entonces es conveniente utilizarlos cada vez que necesitemos una coleccin en la que no importe el orden, pero que necesitemos preguntar si un elemento est o no. Como, a diferencia de Collection, el orden no necesariamente es preservado, no existen mtodos para obtener el primer elemento.

HashSet
Existen varias implementaciones de Set. La ms comunmente usada es HashSet. Los Sets (y los Maps) aprovechan cierta cosa caracterstica de Java: Todos los objetos heredan de Object, por lo tanto todos los mtodos de la clase Object estn presentes en todos los objetos. Hay ciertas cosas que todo objeto en Java sabe hacer. Dos de estas cosas son:

Saber si es igual a otro, con su mtodo equals(). Devolver un nmero entero de modo tal que si dos objetos son iguales ese nmero tambin lo ser (se conoce esto como un hash). Esto todo objeto lo sabe hacer con su mtodo hashCode().

La clase HashSet aprovecha la segunda de las funciones. A cada objeto que se aade a la coleccin se le pide que calcule su hash. Este valor ser un nmero entre 2147483647 y 2147483648. Basado en ese valor se lo guarda en una tabla. Ms tarde, cuando se pregunta con contains() si un objeto x ya est, habr que saber si est en esa tabla. En qu posicin de la tabla est? HashSet puede saberlo, ya que para un objeto determinado, el hash siempre va a tener el mismo valor. Entonces la funcin contains de HashSet saca el hash del objeto que le pasan y va con eso a la tabla. En la posicin de la

tabla hay una lista de objetos que tienen ese valor de hash, y si uno de esos es el buscado contains devuelve true. Un efecto de este algoritmo es que el orden en el que aparecen los objetos al recorrer el set es impredecible. Tambin es importante darse cuenta de que es crtico que la funcin hashCode() tiene que devolver siempre el mismo valor para los objetos que se consideran iguales (o sea que equals() da true). Si esto no es as, HashSet pondr al objeto en una posicin distinta en la tabla que la que ms adelante consultar cuando se llame a contains, y entonces contains dar siempre falso, por ms que se haya hecho correctamente el add. Esto mismo puede suceder si se usan como claves objetos que varen.

TreeSet
Antes de entrar en la descripcin de TreeSet vaya una breve explicacin. Otra cosa que pueden saber hacer los objetos con independencia de cmo y dnde son usados es saber ordenarse. A diferencia de equals y hashCode, que estn en todos los objetos, la capacidad de ordernarse est slo en aquellos que implementan la interfaz Comparable. Cuando un objeto implementa esta interfaz promete saber compararse con otros (con el mtodo compareTo()), y responder con este mtodo si l est antes, despus o es igual al objeto que se le pasa como parmetro. Al orden resultante de usar este mtodo se le llama en Java orden natural. Muchas de las clases de Java implementan Comparable, por ejemplo String lo hace, definiendo un orden natural de los strings que es el obvio, el alfabtico. Tambin implementan esta interfaz Date, Number, etc. y los rdenes naturales que definen estas implementaciones tambin los los que uno esperara. Si yo creo una clase ma llamada Alumno, queda a mi cargo, si as lo quiero, la definicin de un orden natural para los alumnos. Puedo elegir usar el apellido, el nombre, el nmero de matrcula, etc. De acuerdo al atributo que elija para definir el orden natural codificar el mtodo compareTo(). Lo que es importante es que la definicin de este mtodo sea compatible con el equals(); esto es que a.equals(b) si y slo si a.compareTo(b) == 0. TreeSet usa una tcnica completamente diferente a la explicada para HashSet. Construye un rbol con los objetos que se van agregando al conjunto. Un rbol es una forma en computacin de tener un conjunto de cosas todo el tiempo en orden, y permitir que se agreguen ms cosas y que el orden se mantenfga. Al tener todo en orden TreeSet puede fcilmente saber si un objeto est, por medio de una tcnica similar a la explicada en el comienzo de este artculo para buscar los nombres de las notas. Una ventaja de TreeSet es que el orden en el que aparecen los elementos al recorrerlos es el orden natural de ellos (los objetos debern implementar Comparable, como lo explico arriba; si no lo hacen se deber especificar una funcin de comparacin manualmente). Una desventaja es que mantener todo ordenado tiene un costo, y esta clase es un poquito menos eficiente que HashSet.

Map
Un Map representa lo que en otros lenguajes se conoce como diccionario y que se suele asociar a la idea de tabla hash (aunque no se implemente necesariamente con esa tcnica). Un Map es un conjunto de valores, con el detalle de que cada uno de estos valores tiene un objeto extra asociado. A los primeros se los llama claves o keys, ya que nos permiten acceder a los segundos. Cuando digo que las claves forman un conjunto, lo digo en el sentido Java: Son un Set, no puede haber duplicados. En otros lenguajes existen estructuras parecidas que admiten la existencia de claves duplicadas (a veces conocidos como multimap). La nica manera de lograr esto en Java es haciendo que el map guarde listas de valores en vez de los valores sueltos. Un Map no es una Collection ya que esa interfaz le queda demasiado chica. Podramos decir que Collection es unidimensional, mientras que un Map es bidimensional. No hay una manera trivial de expresar un Map en una simple serie de objetos que podemos recorrer. S podramos recorrer una serie de objetos si cada uno de ellos representase un par {clave, valor} (y de hecho eso se puede hacer). Pero esta forma de recorrer un Map no es la forma primaria en que se usa. Algunos de los mtodos ms importantes de un Map son: get(Object clave) Obtiene el valor correspondiente a una clave. Devuelve null si no existe esa clave en el map. put(K clave, V valor) Aade un par clave-valor al map. Si ya haba un valor para esa clave se lo reemplaza. Adems hay algunas funciones que son tiles a la hora de recorrer eficientemente el contenido de un Map: keySet() Todas las claves (devuelve un Set, es decir, sin duplicados). values() Todos los valores (los valores s pueden estar duplicados, por lo tanto esta funcin devuelve una Collection). entrySet() Todos los pares clave-valor (devuelve un conjunto de objetos Map.Entry, cada uno delos cuales devuelve la clave y el valor con los mtodos getKey() y getValue() respectivamente).

Queue y Deque
Se conoce como cola a una coleccin especialmente diseada para ser usada como almacenamiento temporario de objetos a procesar. Las operaciones que suelen admitir las colas son encolar, obtener siguiente, etc. Por lo general las colas siguen un patrn que en computacin se conoce como FIFO (por la sigla en ingls de First In First Out - lo que entra primero, sale primero), lo que no quiere decir otra cosa que

lo obvio: El orden en que se van obteniendo los siguientes objetos coincide con el orden en que fueron introducidos en la cola. Esto anlogo a su tocaya del supermercado: La gente que hace la cola va siendo atendida en el orden en que lleg a sta. Hasta hace poco, para implementar una cola FIFO en Java la nica opcin provista por la biblioteca de colecciones era LinkedList. Como ya se dijo ms arriba, esta implementacin ofrece una implementacin eficiente de las operaciones poner primero y sacar ltimo. Sin embargo, aunque la implentacin es la correcta, a nivel de interfaz dejaba algo que desear. Los mtodos necesarios para usar una LinkedList como una cola eran parte solamente de la clase LinkedList, no exista ninguna interfaz que abstraiga el concepto de cola. Esto haca imposible crear cdigo genrico que use indistintamente diferentes implementaciones de colas (que por ejemplo no sean FIFO sino que tengan algn mecanismo de prioridades). Esta situacin cambi recientemente a partir del agregado a Java de dos interfaces expresamente diseadas para el manejo de colas: La interfaz Queue tiene las operaciones que se esperan en una cola. Tambin se cre Deque, que representa a una double-ended queue, es decir, una cola en la que los elementos pueden aadirse no solo al final, sino tambin empujarse al principio.

Jerarqua de las clases del framework

Tomado de http://www.reloco.com.ar/prog/java/collections.html Autor: Nicols Lichtmaier.

También podría gustarte