Tema 3

Anuncio
Tema 3. Tipos List y Set
Autor: Miguel Toro.
Revisión: José C. Riquelme
1.
Definiciones
Java proporciona además del tipo array, los tipos List y Set para gestionar agregados de
elementos del mismo tipo. Los objetos de los tipos array y List son agregados lineales de
elementos que pueden ser accedidos (indexados) a través de la posición que ocupan en la
colección. El tipo Set no es indexable. Para poderlos usar debe importarse el paquete java.util.
Los elementos que puede contener un de agregado de tipo List o Set tienen que ser
obligatoriamente objetos, por el contrario de los arrays que podían contener también tipo
primitivos. Esto quiere decir que no se puede definir un List<int> o un Set<double> y se ha de
recurrir a los tipos envoltorios.
En resumen:
Pueden contener
tipos primitivos
array
Sí
List
Set
String
No
No
Sólo char
Pueden contener Se puede
tipos objeto
modificar el
tamaño
Sí
No
Sí
Si
No
Sí
Si
No
Puede modificarse
el contenido de una
celda
Sí
Sí
No
No
La inicialización para los casos de List<T> y Set<T> se hace con el operador new y el
constructor de la clase adecuado. Hay que tener en cuenta que List y Set son interfaces y que
Java nos proporciona clases que implementas esos tipos. Las clases que implementan List son
ArrayList y LinkedList que tienen características de eficiencia diferente según cuál sea el uso
mayoritario que se vaya a hacer de la lista. Si preferiblemente se va a usar para acceder a un
elemento mediante índice o buscarlo es recomendable ArrayList. Por el contrario, si la mayoría
de las operaciones son de inserción y borrado es preferible LinkedList. Al final del tema se le
propone un ejercicio sobre las diferencias entre ambas implementaciones. Finalmente, la clase
más usada para implementar un objeto de tipo Set es HashSet, aunque hay algunas
implementaciones especiales como EnumSet o LinkedHashSet.
Ejemplos:
List<Integer> listanumeros = new ArrayList<Integer>();
Set<Punto> nube = new HashSet<Punto>();
La diferencia entre List y Set es que en este último tipo no se permiten elementos repetidos,
esto es, si dos elementos x e y son tales que x.equals(y) devuelve true, al insertar los dos sólo
uno estará en el Set. Hay que tener en cuenta que al redefinir el método equals Java obliga a
2
Introducción a la Programación
redefinir el método de Object denominado hashCode. Este método asigna a cada objeto de
cualquier clase un único número entero de identificador, que es usado por la clase HashSet
para insertar elementos en un conjunto. Por tanto, si el método hashCode de Punto no está
implementado, el siguiente código añadiría al conjunto nube 4 puntos aunque haya dos
iguales.
Set<Punto> nube = new HashSet<Punto>();
Punto
Punto
Punto
Punto
p = new PuntoImpl(2.,0.);
p1 = new PuntoImpl(2.,0.);
p2 = new PuntoImpl(3.,0.);
p3 = new PuntoImpl(4.,0.);
nube.add(p);nube.add(p1);nube.add(p2);nube.add(p3);
mostrar(nube);
El método hashCode debe redefinirse siempre invocando a los métodos hashCode de sus
propiedades, normalmente mediante una suma de éstos multiplicados por algún número
primo. Por ejemplo, el hashCode de Punto quedaría así:
public int hashCode(){
return getX().hashCode()*31+getY().hashCode();
}
2. Métodos comunes de List y Set
Tanto List como Set heredan en Java de la interfaz Collection. Por tanto los métodos de
Collection son comunes a ambos tipos. A continuación presentamos los más usados. La lista
completa de métodos puede consultarse en:
http://docs.oracle.com/javase/7/docs/api/java/util/Collection.html
boolean add(E e)
Añade un elemento a la colección, devuelve false si no se añade.
boolean addAll(Collection c)
Añade todos los elementos de c a la colección que invoca. Es eel operador unión
void
clear()
Borra todos los elementos de la colección
boolean contains(Object o)
Devuelve true si o está en la colección invocante.
boolean containsAll(Collection c)
Devuelve true si la colección que invoca contiene todos los elementos de c
2. Elementos del lenguaje Java
boolean isEmpty()
Devuelve true si la colección no tiene elementos
boolean remove(Object o)
Borra el objeto o de la colección que invoca, si no estuviera se devuelve false
boolean removeAll(Collection c)
Borra todos los objetos de la colección que invoca que estén en c
boolean retainAll(Collection c)
En la colección que invoca sólo se quedarán aquellos objetos que están en c. Por tanto, es
la intersección entre ambas colecciones.
int
size()
Devuelve el número de elementos
T[]
toArray(T[] a)
Devuelve un array con la colección
3. Métodos específicos de List
Además de los métodos anteriores, como se ha señalado anteriormente List es un tipo
indexado, lo que permite añadir o borrar el elemento de una determinada posición. Más en
http://docs.oracle.com/javase/7/docs/api/java/util/List.html
void
add(int index, E element)
Inserta el elemento especificado en la posición especificada
boolean addAll(int index, Collection c)
Inserta todos los elementos de c en la posición especificada
E
get(int index)
Devuelve el elemento de la lista en la posición especdificada.
int
indexOf(Object o)
Devuelve el índice donde ser encuentra por primera vez el elemento o (si no está devuelve
-1)
int
lastIndexOf(Object o)
Devuelve el índice donde ser encuentra por última vez el elemento o (si no estuviera
devuelve -1)
E
remove(int index)
Borra el elemento de la posición especificada
E
set(int index, E element)
3
4
Introducción a la Programación
Remplaza el elemento de la posición indicada por el que se da como argumento
List<E> subList(int fromIndex, int toIndex)
Devuelve una vista de la porción de la lista entre fromIndex, inclusive, and
toIndex, sin incluir.
4. Anidamiento de agregados
Los elementos de un List pueden ser a su vez también List o un Set. Pensemos por ejemplo en
cualquier organización, por ejemplo un Hospital. Los médicos de un Hospital se organizan en
(una lista de) departamentos, y cada uno de ellos podría ser un conjunto de médicos. De esta
manera:
List<Set <Medico>> hospital = new ArrayList<Set<Medico>>();
Set<Medico> dep1= new HashSet<Medico>();
Set<Medico> dep2= new HashSet<Medico>();
....
dep1.add(medico1);
....
depn.add(medicom);
....
hospital.add(0,dep1);
hospital.add(1,dep2);
....
De la misma forma el tipo para guardar los valores de expresión genética del ADN de un
conjunto de pacientes, se definiría de la siguiente manera.
Set<List<Double>> experimento=new HashSet<List<Double>>();
List<Double> muestraADN1= new ArrayList<Double>();
List<Double> muestraADN2= new ArrayList<Double>();
....
muestraADN1.add(0.78);
....
muestraADNn.add(-0.56);
....
experimento.add(muestraADN1);
experimento.add(muestraADN2);
....
5. Tipos genéricos y métodos genéricos
En Java existe la posibilidad de usar tipos genéricos. Estos son tipos que dependen de
parámetros formales que serán instanciados posteriormente por tipos concretos. Los tipos List
y Set son tipos genéricos y por eso en su definición entre los símbolos <> se pone el tipo real
que van a guardar.
2. Elementos del lenguaje Java
En cualquier clase puede haber métodos genéricos. La declaración de los parámetros genéricos
va entre el tipo devuelto por el método y los modificadores del mismo. Por ejemplo,
supongamos que queremos diseñar un método que rellene y devuelva una colección de
objetos iguales, pero que sea “genérica”. Esto es, queremos que sirva para devolver una lista
de Punto, de Integer o de Circulo.
La construcción del método ha de ser genérica, y el tipo es sustituido por una T. Así el método
recibe un entero con el número de elementos que tendrá la lista y un objeto a de tipo T sin
definir. Su código es:
public static <T> List<T> nCopias(int n, T a){
List<T> v = new ArrayList<T>();
for(int i=0; i<n; i++){
v.add(a);
}
return v;
}
En el programa principal es donde los tipos y métodos deben instanciarse antes de ser usados.
Es decir, los parámetros genéricos deben sustituirse por parámetros concretos. Así a la hora de
usar el método nCopias podríamos poner:
public static void main(String[] args) {
Integer a = 14;
List<Integer> v;
v = nCopias(10,a);
mostrar(v);
Punto p = new PuntoImpl(1.,0.);
List<Punto> lp;
lp=nCopias(5,p);
mostrar(lp);
}
En el ejemplo se ha instanciado el tipo genérico List<T> a List<Integer>. Igualmente se ha
instanciado el método nCopias. Ahora la instanciación ha sido deducida por el compilador. El
mecanismo por el cual el compilador induce la instanciación necesaria se denomina inferencia
de tipos. Java tiene esta característica.
6. ANEXO I
Implemente en una clase TestLista el siguiente código. Este código crea dos listas usando cada
una de las dos implementaciones existentes. A continuación hace lo siguiente en las dos listas:
inserta 1.000.000 de enteros aleatorios secuencialmente; inserta 10.000 elementos en la
posición 0; ; inserta 10.000 elementos en una posición intermedia al azar; hace 5.000
búsquedas de elementos elegidos al azar; borra 5.000 elementos de índices elegidos al azar;
por último, accede a 5.000 posiciones (mediante su índice) elegidas al azar. Para cada una de
5
6
Introducción a la Programación
esas operaciones muestra el tiempo, en milisegundos, empleado en cada una de las dos
implementaciones. Interprete los resultados.
Para ello, tenga en cuenta que debajo de la implementación mediante ArrayList hay un array
de longitud fija en el que se accede a los elementos mediante el índice; cuando el array se
llena, se mueven todos sus elementos a un nuevo array del doble del tamaño del anterior, y así
sucesivamente cada vez que se “necesita más espacio”. Insertar en un punto del array que no
sea el final implica que el elemento que está en la posición en la que se inserta y superiores
deben ser movidos un lugar hacia delante. Lo mismo ocurre cuando se borra: los que están a la
derecha del que se elimina tienen que ser desplazados un lugar hacia la izquierda.
En la implementación mediante LinkedList, los elementos están formando una cadena en la
que cada uno sabe quién es el anterior y el siguiente. Para acceder a uno en concreto (que no
sea el último, que está localizado por la propia lista), debe recorrerse la cadena desde el
principio avanzando por los enlaces hacia delante tantas veces como indique el índice. Para
borrar un elemento simplemente se elimina el eslabón de la cadena, pero para saber cuál es el
que hay que borrar hay que localizarlo, lo que exige recorrer la cadena como en los accesos.
import
import
import
import
public
java.util.ArrayList;
java.util.LinkedList;
java.util.List;
java.util.Random;
class TestLista extends Test {
private static final int NUM_INSERCIONES = 1000000;
private static final int NUM_INSERCIONES_PRINCIPIO = 10000;
private static final int NUM_INSERCIONES_POS = 10000;
private static final int NUM_BUSQUEDAS = 5000;
private static final int NUM_BORRADOS = 5000;
private static final int NUM_ACCESSOS = 5000;
private static final int MAX_VALUE = 100000;
private static Random rand = new Random();
private static long tiempoAntes, tiempoDespues;
public static void main(String[] args) {
List<Integer> arrayList, linkedList;
arrayList = new ArrayList<Integer>();
linkedList = new LinkedList<Integer>();
mostrar("TEST DE INSERCIÓN SECUENCIAL========================");
mostrar("Vamos a insertar " + NUM_INSERCIONES+ " elementos
secuencialmente al final...");
testInsercion(arrayList);
testInsercion(linkedList);
mostrar("TEST DE INSERCIÓN AL PRINCIPIO======================");
mostrar("Vamos a insertar " + NUM_INSERCIONES_PRINCIPIO+ " elementos
al principio...");
testInsercionPrincipio(arrayList);
testInsercionPrincipio(linkedList);
mostrar("TEST DE INSERCIÓN INTERMEDIA======================");
mostrar("Vamos a insertar " + NUM_INSERCIONES_POS+ " elementos
intermedios...");
testInsercionPrincipio(arrayList);
testInsercionPrincipio(linkedList);
mostrar("TEST DE BÚSQUEDA AL AZAR============================");
mostrar("Vamos a buscar " + NUM_BUSQUEDAS + " elementos al azar...");
2. Elementos del lenguaje Java
testBusqueda(arrayList);
testBusqueda(linkedList);
mostrar("TEST DE ACCESO AL AZAR==============================");
mostrar("Vamos a acceder a " + NUM_ACCESSOS + " elementos al
azar...");
testAcceso(arrayList);
testAcceso(linkedList);
mostrar("TEST DE BORRADO ELEMENTOS INICIALES===================");
mostrar("Vamos a borrar " + NUM_BORRADOS+ " elementos del
principio...");
testBorrado(arrayList);
testBorrado(linkedList);
}
private static void testInsercion(List<Integer> l) {
tiempoAntes = System.currentTimeMillis();
for (int i = 0; i < NUM_INSERCIONES; i++) {
l.add(rand.nextInt(MAX_VALUE));
}
tiempoDespues = System.currentTimeMillis();
mostrar(l.getClass().getName() + ":\t" + (tiempoDespues tiempoAntes) + " milisegundos");
}
private static void testInsercionPrincipio(List<Integer> l) {
tiempoAntes = System.currentTimeMillis();
for (int i = 0; i < NUM_INSERCIONES_PRINCIPIO; i++) {
l.add(0, rand.nextInt(MAX_VALUE));
}
tiempoDespues = System.currentTimeMillis();
mostrar(l.getClass().getName() + ":\t" + (tiempoDespues - tiempoAntes)
+ " milisegundos");
}
private static void testInsercionPosicion(List<Integer> l) {
tiempoAntes = System.currentTimeMillis();
for (int i = 0; i < NUM_INSERCIONES_POS; i++) {
l.add(rand.nextInt(MAX_VALUE),rand.nextInt(MAX_VALUE));
}
tiempoDespues = System.currentTimeMillis();
mostrar(l.getClass().getName() + ":\t" + (tiempoDespues - tiempoAntes)
+ " milisegundos");
}
private static void testBusqueda(List<Integer> l) {
tiempoAntes = System.currentTimeMillis();
for (int i = 0; i < NUM_BUSQUEDAS; i++) {
l.contains(rand.nextInt(MAX_VALUE));
}
tiempoDespues = System.currentTimeMillis();
mostrar(l.getClass().getName() + ":\t" + (tiempoDespues - tiempoAntes)
+ " milisegundos");
}
private static void testAcceso(List<Integer> l) {
tiempoAntes = System.currentTimeMillis();
Integer n = 0;
for (int i = 0; i < NUM_ACCESSOS; i++) {
n = l.get(rand.nextInt(NUM_INSERCIONES));
n++; // por hacer algo con la variable
}
7
8
Introducción a la Programación
tiempoDespues = System.currentTimeMillis();
mostrar(l.getClass().getName() + ":\t" + (tiempoDespues - tiempoAntes)
+ " milisegundos");
}
private static void testBorrado(List<Integer> l) {
tiempoAntes = System.currentTimeMillis();
for (int i = 0; i < NUM_BORRADOS; i++) {
l.remove(i);
}
tiempoDespues = System.currentTimeMillis();
mostrar(l.getClass().getName() + ":\t" + (tiempoDespues - tiempoAntes)
+ " milisegundos");
}
}
Una posible ejecución del código anterior (depende de la maquina y de los números aleatorios
generados)
TEST DE INSERCIÓN SECUENCIAL========================
Vamos a insertar 1000000 elementos secuencialmente al final...
java.util.ArrayList:
47 milisegundos
java.util.LinkedList:
172 milisegundos
TEST DE INSERCIÓN AL PRINCIPIO======================
Vamos a insertar 10000 elementos al principio...
java.util.ArrayList:
3635 milisegundos
java.util.LinkedList:
0 milisegundos
TEST DE INSERCIÓN INTERMEDIA======================
Vamos a insertar 10000 elementos intermedios...
java.util.ArrayList:
3027 milisegundos
java.util.LinkedList:
0 milisegundos
TEST DE BÚSQUEDA AL AZAR============================
Vamos a buscar 5000 elementos al azar...
java.util.ArrayList:
609 milisegundos
java.util.LinkedList:
2028 milisegundos
TEST DE ACCESO AL AZAR==============================
Vamos a acceder a 5000 elementos al azar...
java.util.ArrayList:
0 milisegundos
java.util.LinkedList:
9010 milisegundos
TEST DE BORRADO ELEMENTOS INICIALES===================
Vamos a borrar 5000 elementos del principio...
java.util.ArrayList:
1764 milisegundos
java.util.LinkedList:
63 milisegundos
7. ANEXO II
En este experimento vamos a comprobar que los objetos que se incluyen en una colección
(List, Set, array) no son copias de éstos, sino que son el mismo objeto. Para ello vamos a
realizar el siguiente experimento. Copie en la clase TestCopias el siguiente código. En él, se
crea una lista lp1 con tres puntos y se asigna a otra lista lp2 los dos primeros elementos
mediante el método subList (el resultado del experimento no cambia si se hace lp1=lp2). En el
experimento 1 se modifican los elementos de la segunda y vemos qué ocurre con los
elementos de la primera (se modifican). En el segundo experimento se crea una nueva lista y
2. Elementos del lenguaje Java
se asignan a ésta los elementos de la primera; luego se modifican los elementos de la segunda
y vemos cómo quedan los elementos de la primera (también se modifican). Por último, en el
tercer experimento se crea una nueva lista en la que se insertan copias de los elementos de la
primera; se modifican los de la segunda y vemos cómo quedan los de la primera (no se
modifican):
package test;
import java.util.LinkedList;
import java.util.List;
import punto.Punto;
import punto.PuntoImpl;
public class TestCopia extends Test {
public static void main(String[] args) {
List<Punto> lp1;
List<Punto> lp2;
lp1 = new LinkedList<Punto>();
Punto p1 = new PuntoImpl(2.0, 3.0);
Punto p2 = new PuntoImpl(3.0, 2.5);
Punto p3 = new PuntoImpl(1.0, 3.6);
lp1.add(p1);
lp1.add(p2);
lp1.add(p3);
mostrar("lp1: " + lp1);
// Experimento 1
mostrar("Experimento 1");
lp2 = lp1.subList(0, 2);
mostrar("lp2: " + lp2);
for (Punto p : lp2) {
p.setY(5.5);
}
mostrar("lp1 después de modificar lp2: " + lp1);
// Experimento 2
mostrar("Experimento 2");
lp2 = new LinkedList<Punto>();
lp2.addAll(lp1);
for (Punto p : lp2) {
p.setY(6.5);
}
mostrar("lp1 después de modificar lp2: " + lp1);
// Experimento 3
mostrar("Experimento 3");
lp2 = new LinkedList<Punto>();
for (Punto punto : lp1) {
Punto p = punto.copia();
p.setY(7.5);
lp2.add(p);
}
mostrar("lp2: " + lp2);
mostrar("lp1 después de modificar lp2: " + lp1);
}
9
10
Introducción a la Programación
Para hacer este experimento deberá añadir al tipo Punto de un método de copia con el
siguiente código:
public Punto copia(){
return new PuntoImpl(getX(),getY());
}
Descargar