Está en la página 1de 12

Instituto de Tecnología ORT

Carrera de Analista de Sistemas


Materia: Taller de Herramientas de Programación
Colecciones de Datos versión 2021C1-1

Colecciones de Datos

Trabajando con colecciones de datos


Las colecciones de datos son comunes en la programación moderna, y en especial bajo
el paradigma de la Programación Orientada a Objetos.
Existen varios tipos de colecciones, pero nosotros vamos a trabajar con un tipo de
ellas que es muy simple de usar: los ArrayList. Un ArrayList permite mantener juntos y
secuencialmente, bajo una misma estructura, elementos de un mismo tipo. Esto quiere
decir que dentro de una colección podemos guardar en memoria objetos de una misma
clase.
Cuando las colecciones son atributos de una clase se representan en UML mediante
una asociación con cardinalidad uno a muchos. Hay dos formas de representar
estructuras múltiples en UML dependiendo de si el nombre de la estructura se
mostrará como un atributo en el cuerpo de la clase o si se deja sólo en la flecha. La
primera forma, más explícita en relación al código que debe escribirse, es la que
sigue:

En este caso, Clase tiene un miembro llamado miColeccion, que es una colección
(un ArrayList) de objetos de clase TipoElemento. Esta forma, más explícita en cuanto
a lo que se intentará escribir como código, tiene la desventaja de que queda más
“atada” a la implementación pues obliga a que esté, sí o sí, implementada con un
ArrayList.
La otra forma de representar esta relación cuando la colección es atributo de una
clase es la siguiente:

Esta forma es más corta y es la más usada, ya que no está explícitamente atada a la
implementación con ArrayList. Muy pronto veremos que hay otras estructuras múltiples
que se pueden describir en UML de la misma manera o de forma muy similar.
Tal como puede verse en la relación entre las clases, el asterisco representa una
cantidad indeterminada de elementos, que en el caso de los ArrayList es dinámica.

Los ArrayList en Java


En Java, tanto la colección en sí como los elementos contenidos en ella siempre son
objetos. Como tales, los ArrayList tienen métodos y propiedades que no solo nos

Taller de Herramientas de Programación - Colecciones de Datos 1


Instituto de Tecnología ORT - Carrera de Analista de Sistemas
Materia: Fundamentos de Programación
Colecciones de Datos

permiten manipular su contenido sino que también nos permiten saber su estado en
cuanto a la cantidad de elementos que la componen.
A continuación aparecen los métodos que más usaremos (hay más, pero por ahora
no los trataremos):

add(elemento) Permite agregar un elemento al final de la colección.

add(n, elemento) Permite agregar un elemento en la posición señalada


por n. Si esta posición está fuera del rango aceptable
(determinado por la cantidad de elementos de la
colección) se producirá un error de ejecución.

get(n) Permite obtener el elemento n de la colección. Si esta


posición está fuera del rango aceptable (determinado
por la cantidad de elementos de la colección) se
producirá un error de ejecución.

remove(n) Permite extraer el elemento n de la colección (lo


devuelve). Si esta posición está fuera del rango
aceptable (determinado por la cantidad de elementos
de la colección) se producirá un error de ejecución.

remove(elemento) Permite extraer el elemento recibido de la colección. A


diferencia del otro remove que recibe la posición del
elemento a extraer, como ya tiene el elemento
devuelve verdadero o falso dependiendo de si se pudo o
no extraer el elemento (devuelve falso cuando el
elemento indicado no estaba insertado en la colección,
cuando era ajeno a ella).

size() Devuelve un valor entero que indica la cantidad de


elementos contenidos en la colección. Cuando la
colección no tiene ningún elemento este valor es 0
(cero).

isEmpty() Devuelve un valor booleano que indica si la colección


está vacía. Es equivalente a preguntar si count() == 0.

En Java los ArrayList son una implementación especial de List, y por eso también se
los conoce como Listas. No obstante este concepto es más vasto de lo que trataremos
aquí, donde sólo los utilizaremos para crear colecciones dinámicas de datos. Por otro
lado, ese nombre lo usaremos más adelante en el cuatrimestre para referirnos a una
estructura específica.

Taller de Herramientas de Programación - Colecciones de Datos 2


Instituto de Tecnología ORT - Carrera de Analista de Sistemas
Materia: Fundamentos de Programación
Colecciones de Datos

Un diamante para reconocer qué guardar


Ante todo, una colección propone un espacio para guardar elementos, uno detrás del
otro, manipularlos, devolverlos y quitarlos. Pero de antemano no “le interesa” ni sabe
qué clase de elementos podría contener: dado que se pueden crear colecciones de
cualquier tipo de elemento es imposible que el lenguaje pueda incluir todas las
variaciones posibles de elementos existentes y por inventar.
Por eso, para instanciar un ArrayList debemos indicar la clase de elementos que
contendrá: cada uno de los ArrayList que creemos deberá aprender qué clase de
elemento podrá recibir, contener y devolver.
Crear un ArrayList sin especificar la clase de elementos que contendrá está
permitido por Java, pero esto se verá en el entorno de programación con una marca de
alerta o warning. Además, al acceder a sus elementos estos se verán como elementos
“genéricos”, objetos de clase Object, y no se podrá acceder de manera automática y
natural a los atributos y métodos particulares, relacionados a la clase real del objeto.
Para resolver este problema y aprender el formato de sus elementos, los ArrayLists
soportan que en su definición se indique cuál será la clase de los elementos que
podrán agregarse a la estructura.
Como otras clases que conoceremos más adelante (e incluso otras que definiremos y
crearemos nosotros mismos) ArrayList es lo que se llama una clase parametrizada.
Este parámetro es, justamente, el que le indica a la estructura qué clase de elemento
contendrá. Y para pasarle ese parámetro se usan los paréntesis agudos (<>), que dada
su forma cuando están vacíos se conoce como diamante.

Creación de un ArrayList
Como primera medida se debe importar el paquete correspondiente los ArrayList a
partir de la adición de su namespace:

import java.utils.ArrayList;

Una vez agregado el paquete nuestro programa permitirá crear colecciones de la


siguiente manera (podemos hacerlo en dos partes, primero declarando la variable y
luego creando el objeto; aquí está la “versión corta”):

ArrayList<ClaseElemento> coleccion = new ArrayList<>();

Así, para crear una colección de objetos de la clase Persona, por ejemplo los contactos
de una agenda, haremos:

ArrayList<Persona> contactos = new ArrayList<>();

Taller de Herramientas de Programación - Colecciones de Datos 3


Instituto de Tecnología ORT - Carrera de Analista de Sistemas
Materia: Fundamentos de Programación
Colecciones de Datos

Tengamos en cuenta que al hacer esto estamos creando únicamente el contenedor, la


colección vacía, sin ningún elemento. La clase ArrayList posee el método size() que
nos indica la cantidad de elementos que están guardados en la colección. Si
ejecutamos la siguiente instrucción inmediatamente después de crearla, obtendremos
como respuesta un 0:

System.out.println(coleccion.size());

Antes dijimos que esta estructura sólo maneja objetos. En caso de que se necesite
crear una colección de elementos de los tipos nativos del lenguaje (integer, double,
etc.) debemos recurrir a sus contrapartes como clase, conocidas como wrapper classes
o clases envoltorio:

ArrayList<Integer> numeros = new ArrayList<>();

Recordemos que String es una clase. Por lo tanto se pueden crear ArrayLists de
cadenas de caracteres directamente:

ArrayList<String> palabras = new ArrayList<>();

ArrayList como atributo, ArrayList como variable local


A la hora de declararlos y crearlos es importante tener en cuenta que no es lo mismo
cuando hay colecciones como atributos de una clase que cuando la colección sólo es
una variable local más dentro de un método, sea para devolverla a través de un
return o para consumirla dentro del mismo método.

En el primer caso siempre deberá declararse entre los atributos de la clase y crearse
dentro del constructor de la clase. No debe declararse dentro del constructor ya que
de esa forma sería una variable local y desaparecería al salir del constructor, y se
crea dentro del mismo porque es el único método que se ejecutará indefectible e
inmediatamente al crear la clase que contiene la colección. También debe tenerse
en cuenta que la colección puede ser creada pero que puede permanecer vacía hasta
el momento en el cual se le agregue algún elemento.

Cuando la colección se define (declara e inicializa) dentro del cuerpo de un método


puede declararse y crearse en un solo paso, como acabamos de ver unas líneas más
arriba.

Taller de Herramientas de Programación - Colecciones de Datos 4


Instituto de Tecnología ORT - Carrera de Analista de Sistemas
Materia: Fundamentos de Programación
Colecciones de Datos

Agregar un elemento a un ArrayList


Para agregar un elemento a un ArrayList utilizaremos el método add(elemento), el
cual permite agregar un nuevo elemento al final de la estructura (es decir, como
último elemento). Hay forma de indicar específicamente dónde insertar el nuevo
elemento, pero eso excede lo que hoy queremos aprender.

Por ejemplo, para agregar un elemento, podemos hacer:

// creamos el objeto
Objeto elemento = new Objeto();
...
...
...
// lo agregamos a la colección
coleccion.add(elemento);

Solamente en el caso de las clases relacionadas a los tipos nativos (int, double, char,
boolean) podremos agregar elementos sin necesidad de crear explícitamente el objeto
(Java lo hará por nosotros):

numeros.add(19); // insertará el número 19 en la colección

También se puede pasar un primer valor adicional, numérico, que indique en qué
posición de la colección se desea insertar el nuevo elemento. Por ejemplo…

numeros.add(0, 5); // insertará el número 5 adelante de todo

… agrega el elemento con valor 5 en la primera posición de la colección. Este primer


valor, o índice, nunca puede ser menor que 0 o mayor a la cantidad de elementos que
tenga la colección en ese momento. En caso de intentar la inserción de un elemento
más allá de los límites actuales de la estructura (menor a cero o mayor o igual a size())
se producirá un error de ejecución.

¿Qué pasa con null?


Mucho cuidado con null. Así como es válido asignar null a una variable que sirva para
guardar un objeto, null es un valor perfectamente válido y puede agregarse a una
colección como cualquier otro elemento. Ocupará un lugar como cualquier otro
elemento y podrá ser manipulado como cualquier otro valor. Además “calza” en
cualquier ArrayList.
Pero cuidado, porque al obtener el null éste no tendrá ni atributos ni métodos. En

Taller de Herramientas de Programación - Colecciones de Datos 5


Instituto de Tecnología ORT - Carrera de Analista de Sistemas
Materia: Fundamentos de Programación
Colecciones de Datos

caso de poner en un arraylist valores con null primero hay que chequear si es null y
luego usarlo. Por lo tanto se aconseja usar null como elemento en casos muy especiales.

Obtener el elemento n de un ArrayList


Para obtener el elemento n del ArrayList utilizaremos el método get(n), donde n es la
posición en la cual está el elemento. El valor de n debe estar entre 0 y size()-1, caso
contrario se producirá un error de ejecución.
Para obtener el primer elemento de la colección haremos:

Objeto elemento = coleccion.get(0);

Esto devolverá la referencia al elemento sin quitarlo de la lista (no hace una copia).
Entonces, si modificamos el elemento que tenemos en el auxiliar, también se verá
modificado en la lista, pues es el mismo elemento referenciado desde dos lugares
distintos.

Extraer el elemento n de un ArrayList


Para extraer el elemento n del ArrayList utilizaremos el método remove(n), donde n
es la posición en la cual está el elemento. El tratamiento es similar al caso del get():
el valor de n debe estar entre 0 y size()-1, caso contrario se producirá un error en
tiempo de ejecución. A diferencia del get(), remove() extrae el elemento de la
posición, y si este no era el último, su lugar será ocupado por el siguiente elemento (y
así hasta el final), acortando el tamaño de la estructura en 1.
Por ejemplo, para extraer el primer elemento de la colección haremos:

Objeto elemento = coleccion.remove(0);

o, directamente:

coleccion.remove(0);

La diferencia entre estos dos casos es que mientras el primero guarda el elemento
removido en una variable auxiliar para su uso posterior, el segundo lo descarta.
También se puede intentar la remoción de un elemento a partir del mismo (es
decir que, en vez de pasar como parámetro la posición del elemento a extraer, se pasa
el elemento). Esto suele ser útil cuando ya tenemos el elemento, por ejemplo porque
otro método lo buscó, y queremos extraerlo del arraylist sin saber su posición en el
mismo (y ni siquiera saber fehacientemente si está).

coleccion.remove(miElemento);

Taller de Herramientas de Programación - Colecciones de Datos 6


Instituto de Tecnología ORT - Carrera de Analista de Sistemas
Materia: Fundamentos de Programación
Colecciones de Datos

En este caso, el método devuelve un valor booleano que indica si el elemento


fue efectivamente removido de la estructura.

Recorrer el ArrayList completo con for-each


El ciclo conocido como for-each es una variante del for tradicional que, en vez de
utilizar un valor índice para iterar una determinada cantidad de veces, utiliza un
iterador implícito (el equivalente a un señalador en un libro) para visitar a cada uno
de los elementos cargados en la lista, de a uno por vez y del primero al último. Para
eso necesita una variable local en la cual guardará temporalmente la referencia del
elemento visitado.

La estructura del for-each es la que sigue:

… y se expresa en código de la siguiente manera:

for (Clase elementoAuxiliar : coleccion) {


proceso(elementoAuxiliar);
}

En el ejemplo, proceso() deberá reemplazarse por lo que queramos hacer con el


elemento visitado. Por ejemplo, para mostrar el nombre y el número de DNI de cada
persona guardada en la colección, haríamos:

for (Persona ​persona​ : ​personas​){


System.out.println(persona.getNombre());
System.out.println(persona.getDocumento());
}

o, mejor…

for (Persona ​persona​ : ​personas​){


mostrarDatosdePersona(persona);
}

Este ejemplo podría leerse como “para cada persona que esté en la colección de
personas, mostrar el nombre y el número de documento”. Esto hará que en cada
iteración (en cada vuelta del ciclo) la variable persona guarde temporalmente, una a
una, cada ​persona ​que esté en la estructura, comenzando con la primera de todas y
hasta que no haya más. La variable ​persona tendrá asignada en cada ciclo, entonces,
​el objeto de la colección que se está visitando en ese momento.

Taller de Herramientas de Programación - Colecciones de Datos 7


Instituto de Tecnología ORT - Carrera de Analista de Sistemas
Materia: Fundamentos de Programación
Colecciones de Datos

Buscar un elemento en la colección


Utilizar el ciclo for-each es muy fácil y cómodo, pero no tiene una manera natural de
abandonar el recorrido por la colección sin recorrerlo por completo. El inconveniente
de esto es que si estamos buscando un elemento específico y ya lo encontramos, o si
ya sabemos que no lo encontraremos, seguiríamos recorriendo la estructura hasta el
final innecesariamente.
Hay formas de recorrer la estructura parcialmente. Una de ellas es utilizando
explícitamente un iterador, la cual probablemente sea la forma más conocida de
hacerlo. Pero trabajar con iteradores requiere de cierto cuidado y tener en cuenta
algunos conceptos que dejaremos para más adelante.
Entonces nos queda una forma de recorrer la estructura parcialmente sin
necesidad de hacerlo por completo: aprovechando la capacidad de acceder a un
elemento determinado con get(). Por ejemplo, si necesitamos ubicar una persona
determinada de la lista, podríamos hacer lo siguiente:

// Búsqueda de la persona por nro de documento.


// Creamos una variable auxiliar donde dejaremos
// el elemento buscado, si lo encontramos.
Persona personaBuscada = null;
int indice = 0;
// buscamos mientras no hayamos llegado al final
// de la estructura y no hayamos encontrado el
// elemento que estamos buscando.
while (indice < personas.size() && personaBuscada == null) {
if (personas.get(indice).getDocumento() == docBuscado) {
// si la encontramos, la asignamos al auxiliar
personaBuscada = personas.get(indice);
} else {
// incrementamos el índice para pasar al siguiente elemento
indice++;
}
}

El ejemplo anterior utilizará el ciclo while para recorrer la estructura mientras haga
falta. El if interno, como está expresado en los comentarios, cumple dos tareas. La
primera es asignar el elemento al auxiliar cuando es el que buscamos. La segunda, es
darnos la posibilidad de intentar con el siguiente, a ver si es el que queremos. El
incremento de índice puede hacerse también fuera del if (no en el else), aunque en
ese caso perdería la posición donde encontró el elemento. De esta forma, guarda
también la posición y no incremente el índice sin necesidad.
Sin embargo esto mismo se puede hacer de una forma más elegante, más clara y
más propia de un programa orientado a objetos, pues deja la responsabilidad de
identificar el elemento que queremos encontrar a la clase que corresponde. Para esto
debemos separar el código en dos partes: por un lado crear un método en la clase
Persona que podría llamarse, en este caso, mismoDocumento(documento) cuya función

Taller de Herramientas de Programación - Colecciones de Datos 8


Instituto de Tecnología ORT - Carrera de Analista de Sistemas
Materia: Fundamentos de Programación
Colecciones de Datos

sea devolver verdadero o falso dependiendo de la coincidencia del documento propio


del objeto respecto al recibido como parámetro:

public boolean mismoDocumento(int documento) {


return this.documento == documento;
}

Luego sólo resta hacer la búsqueda de la persona utilizando un número de documento


de forma simplificada.

// Búsqueda simplificada de la persona por nro de documento.


Persona personaBuscada = null;
int indice = 0;
// buscamos mientras no hayamos llegado al final de la estructura
// y mientras no hayamos encontrado el mismo documento.
while (indice < personas.size() && !personas.get(indice).mismoDocumento(docBuscado)) {
// incrementamos el índice para pasar al siguiente elemento
indice++;
}

Al salir del ciclo el valor de indice puede ser igual al size() de la colección o menor. Si
vale lo mismo que size() significa que el elemento buscado no fue hallado. Si vale
cualquier otra cosa, en esa posición de la colección tendremos lo que estábamos
buscando. Entonces hacemos:

if (indice < personas.size()) {


personaBuscada = personas.get(indice);
}

Remover algunos de los elementos del ArrayList


Aplicaremos un tratamiento similar cuando queramos extraer algunos elementos de la
colección pero no todos. En este caso, el tema es que al ser los ArrayLists estructuras
dinámicas, al remover un elemento los posteriores se adelantan un lugar. Esto hace
que si incrementamos el valor del índice inmediatamente después de extraer el
elemento, y dado que todos los elementos posteriores se adelantaron una posición, en
la posición que acabamos de abandonar ha quedado, si existe, un elemento que no
hemos visitado: lo habremos salteado.
Para que esto no suceda, sólo debemos avanzar cuando el elemento recién visitado
no haya sido extraído. Si lo extraemos nos quedaremos en esa posición esperando a
que los elementos se reacomoden (que todos los posteriores ocupen su nuevo lugar,
cosa que es automática). Así no nos pasaremos por alto el elemento que “cayó” en la
posición donde estamos parados.

Taller de Herramientas de Programación - Colecciones de Datos 9


Instituto de Tecnología ORT - Carrera de Analista de Sistemas
Materia: Fundamentos de Programación
Colecciones de Datos

En el siguiente ejemplo tenemos una colección de números con los valores 1, 7, 4,


9, 17, 22. No es necesario que estén ordenados. Lo que intentaremos es eliminar los
pares para dejar sólo los números impares:

// Eliminando todos los números pares de la colección.


int indice = 0;
while (indice < numeros.size()) {
if (numeros.get(indice) % 2 == 0) { // si es par
numeros.remove(indice);
} else {
indice++;
}
}

Así, recorreremos la colección de números hasta llegar al 4 (índice=2, recordemos que


el primero tiene índice=0). Entonces, lo extraeremos, y todos los elementos
posteriores se adelantarán un lugar. La colección, entonces, quedará así:

1, 7, 9, 17, 22

Seguiremos parados en la posición 2, donde ahora, en vez del 4 que extrajimos, está el
9. Gracias a que no avanzamos, en la nueva iteración del ciclo (donde podría haber
encontrado un nuevo número par) trataremos el 9, luego el 17 y así hasta llegar al 22.
Aquí también nos detendremos a extraer el número y no incrementaremos, pero la
lista habrá perdido un nuevo elemento (el 22), y quedarán sólo con 4 elementos (1, 7,
9, 17). Como el ciclo sólo itera mientras el índice sea menor a la cantidad de
elementos de la colección, saldremos y la lista quedará solamente con los números
impares.

Colecciones de objetos dentro de otros objetos


Uno de los usos más comunes de las colecciones es guardar listas de valores dentro de
objetos contenedores: la lista de socios de un club, los goles hechos en un partido de
fútbol, los ítems de una factura de compra, etc.
En estos casos, exponer directamente la colección suena a peligro, ya que exponer
públicamente un atributo hace que no sepamos qué es lo que se hace con él fuera de
la clase. Por lo general, necesitamos ofuscar u ocultar estas colecciones como
atributos privados de la clase que los contiene, y proveer de métodos indirectos de
acceso a la colección para no romper el encapsulamiento.
Veamos entonces algunas formas de proveer este acceso.

Creación de colecciones como atributo de una clase


Supongamos que tenemos la clase Club, y que el club mantiene una lista de sus socios.
Para esto, crearemos el atributo privado socios, que será un ArrayList de elemento de
clase Socio. Si bien podemos instanciar aquí la colección, sólo la declararemos y le

Taller de Herramientas de Programación - Colecciones de Datos 10


Instituto de Tecnología ORT - Carrera de Analista de Sistemas
Materia: Fundamentos de Programación
Colecciones de Datos

asignaremos el valor null para que la colección sea creada explícitamente en el


constructor de la clase Club, pues consideramos que es el mejor lugar para hacerlo:

public class Club {

private ArrayList<Socio> socios = null;

public Club() {
this.socios = new ArrayList<Socio>();
}

public void agregarSocio(Socio socio) {


socios.add(socio);
}

public void agregarSocio(String nombre) {


socios.add(new Socio(nombre));
}
}

También podemos ver dos métodos distintos para agregar socios, el primero pasándole
directamente un objeto de la clase Socio, y el segundo que sólo recibe el nombre del
nuevo socio, que será instanciado y agregado en el momento (sobrecarga).
¿Y qué deberíamos hacer para dar de baja un socio? ¿Cómo hacemos para devolver
la instancia de socio que queremos eliminar sin exponer la colección completa? Tal vez
podríamos hacer algo como lo que sigue:

public Socio darDeBaja(int numero) {


Socio socio = null;
int indice = 0;
while (indice < socios.size() && !socio.mismoNumero(numero)) {
indice++;
}
if (indice < socios.size()) {
socio = socios.remove(indice);
}
return socio;
}

.. o aún una versión más resumida que no requiere crear una variable auxiliar:

public Socio darDeBaja(int numero) {


int indice = 0;
while (indice < socios.size() && !socio.mismoNumero(numero)) {
indice++;
}
return (indice < socios.size()) ? socios.remove(indice) : null;
}

Taller de Herramientas de Programación - Colecciones de Datos 11


Instituto de Tecnología ORT - Carrera de Analista de Sistemas
Materia: Fundamentos de Programación
Colecciones de Datos

Así devolvemos null o el socio encontrado, el cual ya ha sido removido de la colección,


para que desde donde han llamado a la rutina se procese la instancia de la manera que
se desee (por ejemplo, para mostrar los datos del socio dado de baja).

Devolver una colección secundaria


Siguiendo con el ejemplo anterior, supongamos que queremos devolver una colección
con todos los socios vitalicios del club. Para eso podríamos hacer algo como lo que
sigue:

public ArrayList<Socio> obtenerVitalicios() {


ArrayList<Socio> vitalicios = new ArrayList<Socio>();
for (Socio s: socios) {
if (s.esVitalicio()) {
vitalicios.add(s);
}
}
return vitalicios;
}

En este caso agregará a la colección vitalicios (creada como variable local) todos
aquellos socios que cumplan con la condición de ser vitalicios.
En este caso, donde los mismos objetos que se encuentran en el arraylist original se
insertan en el arraylist secundario que será devuelto por el método y por lo tanto
compartido, corremos ciertos riesgos de exposición de datos (un caso similar a cuando
nos pasan objetos desde el exterior de la clase y los insertamos directamente).
Una de las formas de solucionar el problema es evaluar, a la hora de devolver un
elemento, si realmente hay que devolver ese elemento, su contenido, o un objeto
ad-hoc creado sólo con los datos necesarios y sin métodos (o sólo con los necesarios).
De esa manera, al crear nuevos objetos que son los que se devolverán, nuestros datos
quedan protegidos.

Taller de Herramientas de Programación - Colecciones de Datos 12

También podría gustarte