Implementamos un ArrayList Genérico en Java

COMPARTIR EN REDES SOCIALES

En esta entrada implementamos un ArrayList genérico en Java que permite guardar cualquier tipo de objeto en su interior. Cabe destacar que implementamos la clase desde 0, es decir, implementamos todos y cada uno de sus métodos y explicamos cada uno de ellos.

¿ Qué métodos vamos a implementar en nuestro ArrayList genérico en Java?

Los métodos que vamos a implementar son los mismos que implementamos en nuestra entrada de «Implementamos un ArrayList de Strings desde 0» :

  1. El método «add», que nos servirá para añadir elementos a nuestro ArrayList
  2. El método «get», que nos servirá para obtener un elemento en una determinada posición.
  3. El método «size», que nos devolverá el número de elementos que tenemos ocupados en nuestro ArrayList.
  4. El método «getCapacity», que nos devolverá el número máximo de elementos que podemos almacenar en un momento determinado. Cabe destacar que esta estructura de datos se va a redimensionar de forma automática y transparente para quien vaya a ser usuario de nuestra clase.
  5. El método «remove», que servirá para eliminar un determinado objeto de nuestro ArrayList.
  6. El método «replace», que utilizaremos para reemplazar un determinado objeto de nuestro ArrayList.
  7. El método «indexOf», que nos devolverá la posición de un determinado elemento si existe en nuestro ArrayList.
  8. El método «contains», que nos devolverá «true» o «false» en función de si existe un determinado elemento en nuestro ArrayList.
  9. El método «clear», que permitirá borrar todo el contenido de nuestro ArrayList.
  10. El método «isEmpty», que nos permitirá saber si nuestro ArrayList está vacío.
  11. Por último, el método «toString», que nos permitirá imprimir todo el contenido.

La idea es implementar los métodos principales de la clase ArrayList de Java:

https://docs.oracle.com/javase/8/docs/api/java/util/ArrayList.html

¿ Qué atributos de clase vamos a utilizar ?

Los atributos de clase que vamos a utilizar son los siguientes:

private final static int INITIAL_SIZE = 10;
private E [] items;
private int usedElements = 0;
private int capacity;
  1. INITIAL_SIZE será una constante que utilizaremos para dar un tamaño inicial a nuestro ArrayList en el constructor
  2. items será nuestra estructura de datos interna para almacenar los elementos. Es importante destacar que el tipo de Array que creamos es de tipo «E», que significa parametrizable.
  3. usedElements será el índice que utilizaremos para saber en que posición tenemos que insertar un determinado elemento.
  4. capacity será el número máximo de elementos que podemos almacenar antes de redimensionar nuestra estructura de datos.

Veamos el constructor de nuestro ArrayList genérico

public MiArrayListGenerico() {
	items = (E[]) new Object[INITIAL_SIZE];
	capacity = INITIAL_SIZE;
}
  1. Inicializamos items indicando que almacenará objetos de tipo «E».
  2. Nuestra capacidad inicial la asignamos a nuestra constante INITIAL_SIZE.

¿ Cómo se instanciaría nuestro ArrayList genérico?

Veamos un ejemplo:

//Declaramos e instanciamos nuestro ArrayList Generico parametrizado para //Productos
MiArrayListGenerico<Producto> misProductos = new MiArrayListGenerico<Producto>();

Es importante destacar que en la declaración e instanciación declaramos el tipo de objeto que va a almacenar nuestro ArrayList. Veamos los métodos ahora, empezando por el método «add».

El método add

El método «add» sirve para almacenar nuevos objetos en nuestro ArrayList. Veamos el código:

public void add(E newItem) {
        //Primero comprobamos si tenemos espacio y si es necesario redimensionar  el array
	if (usedElements < capacity - 1) {
		//No hace falta redimensionar
		items[usedElements] = newItem;
	}else {
	       //Es necesario redimensionar, incrementamos el tamaño actual por 2
		capacity = capacity * 2;
		E [] itemsAux = (E[]) new Object[capacity];
		//Ahora copiamos todos los elementos
		for (int i = 0; i < usedElements; i++) {
			itemsAux[i] = this.items[i];
		}
			
		this.items = itemsAux;
		this.items[usedElements] = newItem;
	}
		
	usedElements++;
}

Vamos a explicar el funcionamiento de este método, ya que es el método que inserta elementos y redimensiona nuestra estructura de datos.

  1. Lo primero que realizamos es comprobar si tenemos tamaño suficiente para almacenar nuestro nuevo elemento. En este punto pueden pasar 2 cosas, si tenemos tamaño insertamos el elemento, si no tenemos tamaño redimensionamos la estructura. Ahora vemos en detalle la redimensión.
  2. Para redimensionar nuestra estructura de datos lo primero que hacemos es multiplicar el valor de la variable capacity por 2, es decir, en la primera redimensión sería 10 * 2 = 20.
  3. Posteriormente creamos un nuevo Array de memoria estática de capacidad de «capacity».
  4. Copiamos todos los elementos que tenemos actualmente en el Array.
  5. Intercambiamos los punteros, jajaja, sí, en Java también existen los punteros pero nos referimos a ellos como referencias de memoria.
  6. Como ya tenemos tamaño, almacenamos el elemento.
  7. Por último incrementamos la variable de «usedElement» en una unidad.

Es importante que os fijéis en el parámetro de entrada a la función: add(E newItem). Mediante la E indicamos que puede ser cualquier tipo de objeto.

La redimensión y su ciclo de vida la podemos ver en la siguiente imagen:

Ciclo de vida de la redimensión de nuestro ArrayList

El método get

El método «get» nos permite obtener un elemento de nuestra estructura de datos mediante un entero que marcará su posición.

public E get(int position) {
	return this.items[position];
}

Al igual que en el caso anterior hacemos uso del parámetro especial E, y por lo tanto, devolvemos un Objeto de tipo «E».

El método size

El método «size» nos sirve para saber cuantos elementos tenemos ahora mismo ocupados en nuestro ArrayList.

public int size() {
    return this.usedElements;
}

Es muy sencillo, simplemente devolvemos nuestro índice interno que marca cuantos elementos tenemos ocupados.

El método getCapacity

El método «getCapacity» nos sirve para saber en un momento determinado cuantos elementos como máximo sin redimensión podríamos almacenar.

public int getCapacity() {
    return this.capacity;
}

Este método también es muy sencillo, devolvemos el valor de nuestro atributo «capacity».

El método remove

El método «remove» nos permite eliminar un elemento pasado por parámetro de entrada. Este método tiene algo de historia y por lo tanto lo explicaremos en detalle.

public boolean remove(E itemToRemove) {
	boolean found = false;

	for (int i = 0; i < this.usedElements; i++) {
		if (this.items[i].equals(itemToRemove)) {
			//Hemos encontrado el elemento
			this.items[i] = this.items[i + 1];
			found = true;
		}else if (found) {
			this.items[i] = this.items[i + 1];
		}
	}
		
	if (found) {
		this.items[usedElements - 1] = null;
		usedElements--;
	}
		
	return found;
}
  1. Lo primero que hacemos es declarar una variable de tipo boolean «found» que utilizaremos para 2 cosas principalmente, por una parte para devolver el resultado en caso de que el elemento que estamos intentando borrar exista y por otra parte para saber cuando debemos realizar el desplazamiento de elementos que veremos posteriormente.
  2. Mediante un bucle for recorremos nuestra estructura de datos interna hasta que encontremos el elemento a borrar.
  3. Una vez encontrado, para borrarlo lo que hacemos es asignar en la posición a borrar la posición del siguiente elemento.
  4. Para todos los elementos posteriores al borrado los desplazamos una posición hacía la izquierda, veamos un ejemplo: tenemos el siguiente array [ 1 2 3 4 5 6 ], queremos borrar el elemento cuyo valor es «4», [ 1 2 3 ] se quedan como están, en «4» ponemos «5», en «5» ponemos «6» y en «6» ponemos «null». Resultado: [1 2 3 5 6 null ], hemos borrado el elemento «4».
  5. Por último, reducimos el número de elementos ocupados en 1.

Fijaos en el parámetro de entrada, utilizamos «E» como tipo de datos.

El método replace

El método «replace» es bastante sencillo y nos sirve para reemplazar un elemento por otro en una posición determinada.

public boolean replace(int position, E newItem) {
	boolean result = false;
		
	if (position < this.usedElements) {
		this.items[position] = newItem;
	}
		
	return result;
}

Simplemente si no nos salimos de los límites sobrescribimos el elemento.

Fijaos en el parámetro de entrada, utilizamos «E» como tipo de datos.

El método indexOf

El método «indexOf» nos devuelve la posición de un determinado elemento en caso de que exista o -1 en caso de que no exista.

public int indexOf(E itemToSearch) {
	int result = -1;
	boolean found = false;
		
	for (int i = 0; i < this.usedElements && !found; i++) {
		if (this.items[i].equals(itemToSearch)) {
			found = true;
			result = i;
		}
	}
		
	return result;
}

Recorremos nuestra estructura de datos interna buscando el elemento y en caso de encontrarlo asignamos su posición a result, que es la variable que devuelve nuestro método.

Fijaos en el parámetro de entrada, utilizamos «E» como tipo de datos.

El método contains

El método «contains» es igual que el método anterior pero en este caso se devuelve «true» o «false» en caso de que exista o no el elemento

public boolean contains(E itemToSearch) {
	boolean found = false;
		
	for (int i = 0; i < this.usedElements && !found; i++) {
		if (this.items[i].equals(itemToSearch)) {
			found = true;
		}
	}
		
	return found;
}

Fijaos en el parámetro de entrada, utilizamos «E» como tipo de datos.

El método clear

El método «clear» nos permite borrar el contenido de todo nuestro Array.

public void clear() {
	this.usedElements = 0;
	this.items = (E[]) new Object[INITIAL_SIZE];
}
  1. Seteamos el valor de la variable «usedElements» a 0.
  2. Volvemos a inicializar nuestra estructura de datos interna a una capacidad de 10.
  3. Resultado: Hemos borrado todo el contenido. El recolector de basura ya realizará el trabajo por nosotros para borrar todos los elementos que antes teníamos.

El método isEmpty

El método «isEmpty» nos permite conocer si nuestro ArrayList está vacio o no.

public boolean isEmpty() {
	return (this.usedElements == 0);
}

Tan simple como devolver si «usedElements» es igual a 0.

El método toString

El método «toString» nos sirve para poder imprimir el contenido de nuestro ArrayList de una forma cómoda y legible de forma humana.

@Override
public String toString() {
	StringBuilder sb = new StringBuilder();
		
	sb.append("[ ");
		
	for (int i = 0; i < this.usedElements; i++) {
		sb.append("{" + this.items[i] + "} ");
	}
		
	sb.append("]");

		
	return sb.toString();
}

Lo devolvemos en formato [ {elemento1} {elemento2} {elemento3} …]

Antes de las pruebas veamos nuestra clase Producto

public class Producto{
	private int id;
	private String nombre;
	
	public Producto(int id, String nombre) {
		this.id = id;
		this.nombre = nombre;
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getNombre() {
		return nombre;
	}

	public void setNombre(String nombre) {
		this.nombre = nombre;
	}
	
	@Override
	public boolean equals(Object p2) {
		Producto producto2 = (Producto)p2;
		if (this.getId() == producto2.getId() && this.getNombre().equals(producto2.getNombre())) {
			return true;
		}else {
			return false;
		}
	}
	
	@Override
	public String toString() {
		return this.getId() + ", " + this.nombre;
	}
	
}

Se trata de una simple clase que contiene 2 atributos, un id y un nombre de producto, y sobrescribe el método «equals» y el método «toString»

¿ Cómo vamos a realizar nuestras pruebas ?

Las pruebas que vamos a realizar van a ser las siguientes:

  1. Inicializamos nuestra clase.
  2. Comprobamos si nuestra estructura está vacía.
  3. Comprobamos la capacidad de nuestra lista.
  4. Creamos 10 productos.
  5. Insertamos los 10 productos en nuestro ArrayList.
  6. Mostramos el contenido del ArrayList.
  7. Comprobamos si nuestra estructura está vacía de nuevo.
  8. Comprobamos la capacidad de nuestra lista de nuevo, debe de haberse redimensionado.
  9. Mostramos cuantos elementos tenemos ocupados.
  10. Eliminamos algunos productos.
  11. Mostramos de nuevo el contenido de nuestro ArrayList.
  12. Mostramos cuantos productos tenemos después del borrado de elementos.
  13. Comprobamos si existe un producto que sabemos que existe.
  14. Comprobamos si existe un producto que sabemos que NO existe.
  15. Localizamos la posición de un producto que sabemos que existe.
  16. Localizamos la posición de un producto que sabemos que NO existe.
  17. Borramos la lista.
  18. Mostramos la lista. Debe de ser vacía.

Veamos el código

//Declaramos e instanciamos nuestro ArrayList Generico parametrizado para Productos
MiArrayListGenerico<Producto> misProductos = new MiArrayListGenerico<Producto>();
//Comprobamos si nuestra lista esta vacía
System.out.println("Nuestra lista está vacía: " + misProductos.isEmpty());
//Comprobamos la capacidad de nuestra lista:
System.out.println("La capacidad de nuestra lista es: " + misProductos.getCapacity());
		
//Insertamos los productos
Producto p1 = new Producto(1, "Manzana");
Producto p2 = new Producto(2, "Platano");
Producto p3 = new Producto(3, "Aguacate");
Producto p4 = new Producto(4, "Fresa");
Producto p5 = new Producto(5, "Naranja");
Producto p6 = new Producto(6, "Ajo");
Producto p7 = new Producto(7, "Tomate");
Producto p8 = new Producto(8, "Lechuga");
Producto p9 = new Producto(9, "Melon");
Producto p10 = new Producto(10, "Sandia");
		
misProductos.add(p1);
misProductos.add(p2);
misProductos.add(p3);
misProductos.add(p4);
misProductos.add(p5);
misProductos.add(p6);
misProductos.add(p7);
misProductos.add(p8);
misProductos.add(p9);
misProductos.add(p10);

//Mostramos los elementos
System.out.println("Mostramos los productos");
System.out.println(misProductos);
		
//Comprobamos si nuestra lista esta vacía
System.out.println("Nuestra lista está vacía: " + misProductos.isEmpty());
//Comprobamos la capacidad de nuestra lista:
System.out.println("La capacidad de nuestra lista es: " + misProductos.getCapacity());
//Mostramos cuantos elementos tenemos ocupados
System.out.println("Tenemos: " + misProductos.size() + " productos");
		
//Eliminamos algunos elementos de nuestra lista. Lechuga y Ajo
misProductos.remove(p8);
misProductos.remove(p6);
		
//Volvemos a mostrar los elementos de nuestro ArrayList
System.out.println(misProductos);
		
//Mostramos cuantos elementos tenemos ocupados
System.out.println("Tenemos: " + misProductos.size() + " productos");

//Comprobamos si existe algún producto que sabemos que existe:
System.out.println("¿Existe el producto platano?: " + misProductos.contains(new Producto(2, "Platano")));
System.out.println("¿Existe el producto Ajo ?: " + misProductos.contains(new Producto(6, "Ajo")));
		
//Comprobamos si existe algún producto que sabemos que existe:
System.out.println("Indice del producto platano: " + misProductos.indexOf(new Producto(2, "Platano")));
System.out.println("Indice del producto Ajo: " + misProductos.indexOf(new Producto(6, "Ajo")));

//Borramos nuestra lista
misProductos.clear();
//Volvemos a mostrar nuestros productos
System.out.println(misProductos);

Veamos la salida de nuestro programa

Nuestra lista está vacía: true
La capacidad de nuestra lista es: 10
Mostramos los productos
[ {1, Manzana} {2, Platano} {3, Aguacate} {4, Fresa} {5, Naranja} {6, Ajo} {7, Tomate} {8, Lechuga} {9, Melon} {10, Sandia} ]
Nuestra lista está vacía: false
La capacidad de nuestra lista es: 20
Tenemos: 10 productos
[ {1, Manzana} {2, Platano} {3, Aguacate} {4, Fresa} {5, Naranja} {7, Tomate} {9, Melon} {10, Sandia} ]
Tenemos: 8 productos
¿Existe el producto platano?: true
¿Existe el producto Ajo ?: false
Indice del producto platano: 1
Indice del producto Ajo: -1
[ ]

Os adjunto el código de las 3 clases de nuestro ArrayList genérico en Java aquí:

Si os ha gustado el artículo, o tenéis cualquier duda sobre el mismo, dejad un comentario en la caja de comentarios.


COMPARTIR EN REDES SOCIALES

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *