Sesion02 Apuntes
Sesion02 Apuntes
Sesion02 Apuntes
Índice
1 Colecciones.................................................................................................................. 2
1.1 Listas de elementos.................................................................................................. 4
1.2 Conjuntos................................................................................................................. 6
1.3 Mapas.......................................................................................................................7
1.4 Wrappers.................................................................................................................. 8
1.5 Genéricos............................................................................................................... 10
1.6 Recorrer las colecciones........................................................................................ 10
1.7 Cuestiones de eficiencia.........................................................................................12
2 Comparación de objetos............................................................................................. 12
2.1 Sobrecarga de equals..............................................................................................13
2.2 Implementación de Comparable............................................................................ 14
2.3 Comparador externo...............................................................................................14
3 Polimorfismo e interfaces...........................................................................................15
3.1 Ejemplo: Algoritmos..............................................................................................16
4 Tipos de datos básicos en las colecciones.................................................................. 16
4.1 Wrappers de tipos básicos......................................................................................16
4.2 Autoboxing............................................................................................................ 16
La plataforma Java nos proporciona un amplio conjunto de clases dentro del que podemos
encontrar tipos de datos que nos resultarán muy útiles para realizar la programación de
aplicaciones en Java. Estos tipos de datos nos ayudarán a generar código más limpio de
una forma sencilla.
Se proporcionan una serie de operadores para acceder a los elementos de estos tipos de
datos. Decimos que dichos operadores son polimórficos, ya que un mismo operador se
puede emplear para acceder a distintos tipos de datos. Por ejemplo, un operador add
utilizado para añadir un elemento, podrá ser empleado tanto si estamos trabajando con
una lista enlazada, con un array, o con un conjunto por ejemplo.
Este polimorfismo se debe a la definición de interfaces que deben implementar los
distintos tipos de datos. Siempre que el tipo de datos contenga una colección de
elementos, implementará la interfaz Collection. Esta interfaz proporciona métodos para
acceder a la colección de elementos, que podremos utilizar para cualquier tipo de datos
que sea una colección de elementos, independientemente de su implementación concreta.
Podemos encontrar los siguientes elementos dentro del marco de colecciones de Java:
• Interfaces para distintos tipos de datos: Definirán las operaciones que se pueden
realizar con dichos tipos de datos. Podemos encontrar aquí la interfaz para cualquier
colección de datos, y de manera más concreta para listas (secuencias) de datos,
conjuntos, etc.
• Implementaciones de tipos de datos reutilizables: Son clases que implementan tipos
de datos concretos que podremos utilizar para nuestras aplicaciones, implementando
algunas de las interfaces anteriores para acceder a los elementos de dicho tipo de
datos. Por ejemplo, dentro de las listas de elementos, podremos encontrar distintas
implementaciones de la lista como puede ser listas enlazadas, o bien arrays de
capacidad variable, pero al implementar la misma interfaz podremos acceder a sus
elementos mediante las mismas operaciones (polimorfismo).
• Algoritmos para trabajar con dichos tipos de datos, que nos permitan realizar una
ordenación de los elementos de una lista, o diversos tipos de búsqueda de un
determinado elemento por ejemplo.
1. Colecciones
2
Copyright © 2012-2013 Dept. Ciencia de la Computación e IA All rights reserved.
Colecciones de datos
boolean add(Object o)
Añade un elemento (objeto) a la colección. Nos devuelve true si tras añadir el elemento la
colección ha cambiado, es decir, el elemento se ha añadido correctamente, o false en caso
contrario.
void clear()
Lo que si podemos hacer es instanciar nosotros un array del tipo adecuado y hacer una
conversión cast ascendente (de tipo concreto a array de objetos), y utilizar el siguiente
método:
String [] cadenas = new String[coleccion.size()];
coleccion.toArray(cadenas); // Esto si que funcionará
Esta interfaz es muy genérica, y por lo tanto no hay ningún tipo de datos que la
implemente directamente, sino que implementarán subtipos de ellas. A continuación
veremos los subtipos más comunes.
3
Copyright © 2012-2013 Dept. Ciencia de la Computación e IA All rights reserved.
Colecciones de datos
Este tipo de colección se refiere a listas en las que los elementos de la colección tienen un
orden, existe una secuencia de elementos. En ellas cada elemento estará en una
determinada posición (índice) de la lista.
Las listas vienen definidas en la interfaz List, que además de los métodos generales de
las colecciones, nos ofrece los siguientes para trabajar con los índices:
void add(int indice, Object obj)
Nos dice cual es el índice de dicho elemento (objeto) dentro de la lista. Nos devuelve -1 si
el objeto no se encuentra en la lista.
Object remove(int indice)
4
Copyright © 2012-2013 Dept. Ciencia de la Computación e IA All rights reserved.
Colecciones de datos
usemos este tipo de datos que debemos controlar la concurrencia de acceso. También
podemos hacer que sea sincronizado como veremos más adelante.
Vector
El Vector es una implementación similar al ArrayList, con la diferencia de que el
Vector si que está sincronizado. Este es un caso especial, ya que la implementación
básica del resto de tipos de datos no está sincronizada.
Esta clase existe desde las primeras versiones de Java, en las que no existía el marco de
las colecciones descrito anteriormente. En las últimas versiones el Vector se ha
acomodado a este marco implementando la interfaz List.
Sin embargo, si trabajamos con versiones previas de JDK, hemos de tener en cuenta que
dicha interfaz no existía, y por lo tanto esta versión previa del vector no contará con los
métodos definidos en ella. Los métodos propios del vector para acceder a su contenido,
que han existido desde las primeras versiones, son los siguientes:
void addElement(Object obj)
Elimina el elemento indicado del vector, devolviendo true si dicho elemento estaba
contenido en el vector, y false en caso contrario.
void removeElementAt(int indice)
5
Copyright © 2012-2013 Dept. Ciencia de la Computación e IA All rights reserved.
Colecciones de datos
métodos para trabajar con dicho vector como si se tratase de una pila, apilando y
desapilando elementos (operaciones push y pop respectivamente). La clase Stack hereda
de Vector, por lo que en realidad será un vector que ofrece métodos adicionales para
trabajar con él como si fuese una pila.
LinkedList
En este caso se implementa la lista mediante una lista doblemente enlazada. Por lo tanto,
el coste temporal de las operaciones será el de este tipo de listas. Cuando realicemos
inserciones, borrados o lecturas en los extremos inicial o final de la lista el tiempo será
constante, mientras que para cualquier operación en la que necesitemos localizar un
determinado índice dentro de la lista deberemos recorrer la lista de inicio a fin, por lo que
el coste será lineal con el tamaño de la lista O(n), siendo n el tamaño de la lista.
Para aprovechar las ventajas que tenemos en el coste temporal al trabajar con los
extremos de la lista, se proporcionan métodos propios para acceder a ellos en tiempo
constante:
void addFirst(Object obj) / void addLast(Object obj)
1.2. Conjuntos
Los conjuntos son grupos de elementos en los que no encontramos ningún elemento
repetido. Consideramos que un elemento está repetido si tenemos dos objetos o1 y o2
iguales, comparandolos mediante el operador o1.equals(o2). De esta forma, si el objeto a
insertar en el conjunto estuviese repetido, no nos dejará insertarlo. Recordemos que el
método add devolvía un valor booleano, que servirá para este caso, devolviendonos true
si el elemento a añadir no estaba en el conjunto y ha sido añadido, o false si el elemento
ya se encontraba dentro del conjunto. Un conjunto podrá contener a lo sumo un elemento
null.
Los conjuntos se definen en la interfaz Set, a partir de la cuál se construyen diferentes
implementaciones:
6
Copyright © 2012-2013 Dept. Ciencia de la Computación e IA All rights reserved.
Colecciones de datos
HashSet
Los objetos se almacenan en una tabla de dispersión (hash). El coste de las operaciones
básicas (inserción, borrado, búsqueda) se realizan en tiempo constante siempre que los
elementos se hayan dispersado de forma adecuada. La iteración a través de sus elementos
es más costosa, ya que necesitará recorrer todas las entradas de la tabla de dispersión, lo
que hará que el coste esté en función tanto del número de elementos insertados en el
conjunto como del número de entradas de la tabla. El orden de iteración puede diferir del
orden en el que se insertaron los elementos.
LinkedHashSet
Es similar a la anterior pero la tabla de dispersión es doblemente enlazada. Los elementos
que se inserten tendrán enlaces entre ellos. Por lo tanto, las operaciones básicas seguirán
teniendo coste constante, con la carga adicional que supone tener que gestionar los
enlaces. Sin embargo habrá una mejora en la iteración, ya que al establecerse enlaces
entre los elementos no tendremos que recorrer todas las entradas de la tabla, el coste sólo
estará en función del número de elementos insertados. En este caso, al haber enlaces entre
los elementos, estos enlaces definirán el orden en el que se insertaron en el conjunto, por
lo que el orden de iteración será el mismo orden en el que se insertaron.
TreeSet
Utiliza un árbol para el almacenamiento de los elementos. Por lo tanto, el coste para
realizar las operaciones básicas será logarítmico con el número de elementos que tenga el
conjunto O(log n).
1.3. Mapas
Aunque muchas veces se hable de los mapas como una colección, en realidad no lo son,
ya que no heredan de la interfaz Collection.
Los mapas se definen en la interfaz Map. Un mapa es un objeto que relaciona una clave
(key) con un valor. Contendrá un conjunto de claves, y a cada clave se le asociará un
determinado valor. En versiones anteriores este mapeado entre claves y valores lo hacía la
clase Dictionary, que ha quedado obsoleta. Tanto la clave como el valor puede ser
cualquier objeto.
Los métodos básicos para trabajar con estos elementos son los siguientes:
Object get(Object clave)
Inserta una nueva clave con el valor especificado. Nos devuelve el valor que tenía antes
dicha clave, o null si la clave no estaba en la tabla todavía.
7
Copyright © 2012-2013 Dept. Ciencia de la Computación e IA All rights reserved.
Colecciones de datos
Este método nos devolverá una enumeración de todas las claves registradas en la tabla.
1.4. Wrappers
La clase Collections aporta una serie métodos para cambiar ciertas propiedades de las
listas. Estos métodos nos proporcionan los denominados wrappers de los distintos tipos
de colecciones. Estos wrappers son objetos que 'envuelven' al objeto de nuestra
colección, pudiendo de esta forma hacer que la colección esté sincronizada, o que la
colección pase a ser de solo lectura.
Como dijimos anteriormente, todos los tipos de colecciones no están sincronizados,
excepto el Vector que es un caso especial. Al no estar sincronizados, si múltiples hilos
utilizan la colección concurrentemente, podrán estar ejecutándose simultáneamente varios
8
Copyright © 2012-2013 Dept. Ciencia de la Computación e IA All rights reserved.
Colecciones de datos
métodos de una misma colección que realicen diferentes operaciones sobre ella. Esto
puede provocar inconsistencias en los datos. A continuación veremos un posible ejemplo
de inconsistencia que se podría producir:
1. Tenemos un ArrayList de nombre letras formada por los siguiente elementos: [ "A",
"B", "C", "D" ]
2. Imaginemos que un hilo de baja prioridad desea eliminar el objeto "C". Para ello hará
una llamada al método letras.remove("C").
3. Dentro de este método primero deberá determinar cuál es el índice de dicho objeto
dentro del array, para después pasar a eliminarlo.
4. Se encuentra el objeto "C" en el índice 2 del array (recordemos que se empieza a
numerar desde 0).
5. El problema viene en este momento. Imaginemos que justo en este momento se le
asigna el procesador a un hilo de mayor prioridad, que se encarga de eliminar el
elemento "A" del array, quedándose el array de la siguiente forma: [ "B", "C", "D" ]
6. Ahora el hilo de mayor prioridad es sacado del procesador y nuestro hilo sigue
ejecutándose desde el punto en el que se quedó.
7. Ahora nuestro hilo lo único que tiene que hacer es eliminar el elemento del índice que
había determinado, que resulta ser ¡el índice 2!. Ahora el índice 2 está ocupado por el
objeto "D", y por lo tanto será dicho objeto el que se elimine.
Podemos ver que haciendo una llamada a letras.remove("C"), al final se ha eliminado el
objeto "D", lo cual produce una inconsistencia de los datos con las operaciones
realizadas, debido al acceso concurrente.
Este problema lo evitaremos sincronizando la colección. Cuando una colección está
sincronizada, hasta que no termine de realizarse una operación (inserciones, borrados,
etc), no se podrá ejecutar otra, lo cual evitará estos problemas.
Podemos conseguir que las operaciones se ejecuten de forma sincronizada envolviendo
nuestro objeto de la colección con un wrapper, que será un objeto que utilice
internamente nuestra colección encargándose de realizar la sincronización cuando
llamemos a sus métodos. Para obtener estos wrappers utilizaremos los siguientes métodos
estáticos de Collections:
Collection synchronizedCollection(Collection c)
List synchronizedList(List l)
Set synchronizedSet(Set s)
Map synchronizedMap(Map m)
SortedSet synchronizedSortedSet(SortedSet ss)
SortedMap synchronizedSortedMap(SortedMap sm)
Como vemos tenemos un método para envolver cada tipo de datos. Nos devolverá un
objeto con la misma interfaz, por lo que podremos trabajar con él de la misma forma, sin
embargo la implementación interna estará sincronizada.
Podemos encontrar también una serie de wrappers para obtener versiones de sólo lectura
de nuestras colecciones. Se obtienen con los siguientes métodos:
Collection unmodifiableCollection(Collection c)
9
Copyright © 2012-2013 Dept. Ciencia de la Computación e IA All rights reserved.
Colecciones de datos
List unmodifiableList(List l)
Set unmodifiableSet(Set s)
Map unmodifiableMap(Map m)
SortedSet unmodifiableSortedSet(SortedSet ss)
SortedMap unmodifiableSortedMap(SortedMap sm)
1.5. Genéricos
Podemos tener colecciones de tipos concretos de datos, lo que permite asegurar que los
datos que se van a almacenar van a ser compatibles con un determinado tipo o tipos. Por
ejemplo, podemos crear un ArrayList que sólo almacene Strings, o una HashMap que
tome como claves Integers y como valores ArrayLists. Además, con esto nos
ahorramos las conversiones cast al tipo que deseemos, puesto que la colección ya se
asume que será de dicho tipo.
Ejemplo
// Vector de cadenas
ArrayList<String> a = new ArrayList<String>();
a.add("Hola");
String s = a.get(0);
a.add(new Integer(20)); // Daría error!!
// HashMap con claves enteras y valores de vectores
HashMap<Integer, ArrayList> hm = new HashMap<Integer, ArrayList>();
hm.put(1, a);
ArrayList a2 = hm.get(1);
A partir de JDK 1.5 deberemos utilizar genéricos siempre que sea posible. Si creamos una
colección sin especificar el tipo de datos que contendrá normalmente obtendremos un
warning.
Los genéricos no son una característica exclusiva de las colecciones, sino que se pueden
utilizar en muchas otras clases, incluso podemos parametrizar de esta forma nuestras
propias clases.
Vamos a ver ahora como podemos iterar por los elementos de una colección de forma
eficiente y segura, evitando salirnos del rango de datos. Dos elementos utilizados
comunmente para ello son las enumeraciones y los iteradores.
Las enumeraciones, definidas mediante la interfaz Enumeration, nos permiten consultar
los elementos que contiene una colección de datos. Muchos métodos de clases Java que
deben devolver múltiples valores, lo que hacen es devolvernos una enumeración que
podremos consultar mediante los métodos que ofrece dicha interfaz.
La enumeración irá recorriendo secuencialmente los elementos de la colección. Para leer
cada elemento de la enumeración deberemos llamar al método:
Object item = enum.nextElement();
10
Copyright © 2012-2013 Dept. Ciencia de la Computación e IA All rights reserved.
Colecciones de datos
Vemos como en este bucle se van leyendo y procesando elementos de la enumeración uno
a uno mientras queden elementos por leer en ella.
Otro elemento para acceder a los datos de una colección son los iteradores. La diferencia
está en que los iteradores además de leer los datos nos permitirán eliminarlos de la
colección. Los iteradores se definen mediante la interfaz Iterator, que proporciona de
forma análoga a la enumeración el método:
Object item = iter.next();
Que nos devuelve el siguiente elemento a leer por el iterador, y para saber si quedan más
elementos que leer tenemos el método:
iter.hasNext()
Además, podemos borrar el último elemento que hayamos leido. Para ello tendremos el
método:
iter.remove();
Por ejemplo, podemos recorrer todos los elementos de una colección utilizando un
iterador y eliminar aquellos que cumplan ciertas condiciones:
while (iter.hasNext())
{
Object item = iter.next();
if(condicion_borrado(item))
iter.remove();
}
Las enumeraciones y los iteradores no son tipos de datos, sino elementos que nos servirán
para acceder a los elementos dentro de los diferentes tipos de colecciones.
A partir de JDK 1.5 podemos recorrer colecciones y arrays sin necesidad de acceder a sus
iteradores, previniendo índices fuera de rango.
Ejemplo
// Recorre e imprime todos los elementos de un array
int[] arrayInt = {1, 20, 30, 2, 3, 5};
for(int elemento: arrayInt)
System.out.println (elemento);
// Recorre e imprime todos los elementos de un ArrayList
11
Copyright © 2012-2013 Dept. Ciencia de la Computación e IA All rights reserved.
Colecciones de datos
2. Comparación de objetos
12
Copyright © 2012-2013 Dept. Ciencia de la Computación e IA All rights reserved.
Colecciones de datos
Todos los Object y clases derivadas tienen un método equals(Object o) que compara
un objeto con otro devolviendo un booleano verdadero en caso de igualdad. El criterio de
igualdad puede ser personalizado, según la clase. Para personalizarlo se puede
sobrecargar el método de comparación:
El método equals no debe sobrecargarse si no es necesario. Sobre todo hay que evitar
sobrecargarlo en casos como los siguientes:
• Cada instancia es intrínsecamente única. Por ejemplo, instancias de hilos, que
representan entidades activas, y no tan sólo un conjunto de valores.
• Cuando no es necesaria una comparación lógica. Por ejemplo, dos números aleatorios,
donde la igualdad puede ocurrir pero su comprobación no es necesaria.
• Una superclase ya sobrecarga equals, y el comportamiento de éste es apropiado para
la clase actual.
Cuando se sobrecarga el método equals se deben cumplir las siguientes propiedades:
• Reflexividad: x.equals(x) devuelve siempre verdadero, si no es nulo.
• Simetría: para cualquier par de instancias no nulas, x.equals(y) devuelve verdadero
si y sólo si y.equals(x) también devuelve verdadero.
• Transitividad: si x.equals(y)==true y y.equals(z)==true, entonces x.equals(z)
también será verdadero, para cualesquiera instancias no nulas.
• Consistencia: múltiples llamadas al método con las mismas instancias devuelven el
mismo resultado.
• Comparación con null falsa: x.equals(null) devuelve falso
Para asegurar la propiedad de consistencia también conviene sobrecargar el método
hashCode, que es necesario para que funcionen correctamente todas las colecciones
basadas en códigos hash, como HashMap, HashSet, Hashtable. Objetos que se
consideren iguales deben devolver hashCode iguales. Debe cumplirse:
• Cuando hashCode es invocado varias veces para el mismo objeto, debe devolver
consistentemente el mismo entero, siempre que no se haya modificado ninguna
13
Copyright © 2012-2013 Dept. Ciencia de la Computación e IA All rights reserved.
Colecciones de datos
Hay algoritmos, como Collections.sort( ), que requieren que los objetos tengan un
método compareTo() que devuelva un número negativo, positivo o cero, según si un
objeto es menor que el otro, mayor, o igual. Este método no viene en Object para poder
sobrecargarlo, sino en la interfaz Comparable que tenemos que implementar, y que nos
obligará a implementar también el método compareTo.
Por supuesto, no todos los objetos se pueden comparar en términos de mayor o menor.
Así, el hecho de que una clase implemente Comparable nos indica que se trata de una
estructura de datos cuyos objetos sí son comparables, y por tanto podrían ordenarse.
Un ejemplo de implementación de Comparable:
En muchas estructuras de datos la ordenación podría ser subjetiva. Por ejemplo, las fichas
de clientes podrían considerarse mayores o menores según el identificador, según el
apellido o según la fecha de alta. La estructura de datos no tiene por qué ofrecer todas las
posibilidades de comparación. En estos casos, en los que no hay un sólo orden inherente a
la estructura de datos, podemos utilizar un comparador externo.
Para ello tenemos que implementar la interfaz Comparator que nos obliga a implementar
el método compare. Al tratarse, una vez más, de una interfaz, podríamos hacerlo dentro
de la propia clase cuyas instancias vamos a comparar, o bien en otra clase aparte, como en
el siguiente ejemplo:
14
Copyright © 2012-2013 Dept. Ciencia de la Computación e IA All rights reserved.
Colecciones de datos
Para hacer uso de ese comparador externo en algún método, debemos indicarlo pasando
una instancia del Comparator. En cambio si queremos utilizar el método de comparación
Comparable.compareTo( ), sobra con que la clase implemente Comparable.
3. Polimorfismo e interfaces
15
Copyright © 2012-2013 Dept. Ciencia de la Computación e IA All rights reserved.
Colecciones de datos
Hemos visto que en Java cualquier tipo de datos es un objeto, excepto los tipos de datos
básicos: boolean, int, long, float, double, byte, short, char.
Cuando trabajamos con colecciones de datos los elementos que contienen éstas son
siempre objetos, por lo que en un principio no podríamos insertar elementos de estos tipos
básicos. Para hacer esto posible tenemos una serie de objetos que se encargarán de
envolver a estos tipos básicos, permitiéndonos tratarlos como objetos y por lo tanto
insertarlos como elementos de colecciones. Estos objetos son los llamados wrappers, y las
clases en las que se definen tienen nombre similares al del tipo básico que encapsulan,
con la diferencia de que comienzan con mayúscula: Boolean, Integer, Long, Float,
Double, Byte, Short, Character.
Estas clases, además de servirnos para encapsular estos datos básicos en forma de objetos,
nos proporcionan una serie de métodos e información útiles para trabajar con estos datos.
Nos proporcionarán métodos por ejemplo para convertir cadenas a datos numéricos de
distintos tipos y viceversa, así como información acerca del valor mínimo y máximo que
se puede representar con cada tipo numérico.
16
Copyright © 2012-2013 Dept. Ciencia de la Computación e IA All rights reserved.
Colecciones de datos
4.2. Autoboxing
Esta característica aparecida en JDK 1.5 evita al programador tener que establecer
correspondencias manuales entre los tipos simples (int, double, etc) y sus
correspondientes wrappers o tipos complejos (Integer, Double, etc). Podremos
utilizar un int donde se espere un objeto complejo (Integer), y viceversa.
Ejemplo
ArrayList<Integer> a = new ArrayList<Integer>();
a.add(30);
Integer n = v.get(0);
n = n+1;
int num = n;
17
Copyright © 2012-2013 Dept. Ciencia de la Computación e IA All rights reserved.
Colecciones de datos
18
Copyright © 2012-2013 Dept. Ciencia de la Computación e IA All rights reserved.