Skip to content

2 ArrayList

Interfaz Collection

Es la interfaz raíz de la jerarquía de las colecciones. Java no proporciona ninguna implementación directa de esta interfaz, sino que proporciona implementaciones de sub-interfaces más específicas como Set y List. Esta interfaz se utiliza normalmente para pasar colecciones y manipularlas cuando se desea la máxima generalidad.

Veamos algunos de los métodos que podemos observar en la API

  • boolean add(E e): añade el elemento e a la colección.
  • boolean addAll(Collection <? extends E> c): añade todos los elementos de la colección c.
  • void clear(): elimina todos los elementos de la colección.
  • boolean contains(Object o): comprueba si el elemento o está en la colección.
  • boolean containsAll(Collection<?> c) : comprueba si todos los elementos de c están en la colección.
  • boolean isEmpty(): comprueba si la colección está vacía.
  • boolean remove(Object o): elimina de la colección el elemento o.
  • boolean removeAll(Collection<?> c): elimina de la colección todos los elementos de c.
  • boolean retainAll(Collection<?> c): elimina de la colección todos los elementos exceptos los que están en c, es decir, obtiene la intersección.
  • int size(): devuelve el tamaño de la colección.

Interfaz List

Esta interfaz es una sub-interfaz o interfaz hija de Collection, por lo tanto, tiene todos los métodos de Collection y además añade los suyos propios.

Este tipo de colección se refiere a listas en las que los elementos de la colección tienen un orden, es decir, existe una secuencia de elementos. Cada elemento tiene un índice o posición. El primer elemento ocupa la posición 0. La interfaz List sí admite elementos duplicados.

Veamos algunos de los métodos que podemos observar en la API:

  • void add(int index, E element): inserta el elemento E en la posición index.
  • boolean add(E e): añade el elemento e al final de la lista.
  • boolean addAll(int index, Collection <? extends E> c): inserta todos los elementos de c en la posición index.
  • boolean addAll(Collection <? extends E> c): añade todos los elementos de c al final de la lista.
  • E get(int index): devuelve el elemento de la posición index.
  • int indexOf(Object o): devuelve el índice de la primera ocurrencia del elemento o en la lista, o -1 si la lista no contiene el elemento.
  • int lastIndexOf(Object o): devuelve el índice de la última ocurrencia del elemento o en la lista, o -1 si la lista no contiene el elemento.
  • E remove(int index): elimina el elemento que se encuentra en la posición index. E set(int index, E element): reemplaza el elemento que se encuentra en index por el elemento element.
  • List<E> subList(int fromIndex, int toIndex): devuelve la sub-lista comprendida entre las posiciones fromIndex incluida y toIndex excluida.

Existen varios tipos de implementaciones realizadas dentro de la plataforma Java para la interfaz List, como por ejemplo, ArrayList.

La clase ArrayList

Se basa en un array redimensionable que aumenta su tamaño según crece la colección de elementos. Lo bueno es que el tiempo de acceso a un elemento en particular es ínfimo. Lo malo es que si queremos eliminar un elemento del principio o del medio, la clase debe mover todos los que le siguen a la posición anterior, para tapar el agujero que deja el elemento removido. Esto hace que sacar elementos del medio o del principio sea costoso.

ArrayList mantiene el orden de inserción, es decir, si recorremos la colección se nos mostrará en el mismo orden en que insertamos los objetos.

Veamos un ejemplo de declaración e inicialización:

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

A partir de java7, no es necesario indicar el genérico en la inicialización:

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

Para hacer el código más genérico, se puede definir la variable de tipo interfaz, ya que dicho código podría funcionar con cualquier clase que implemente la interfaz, simplemente habría que cambiar el new:

List<Integer> list = new ArrayList<>();

Veamos un ejemplo de ArrayList donde utiliza métodos tanto de Collection como de List:

import java.util.ArrayList;
import java.util.List;

public class ShowArrayList{
    public void show(){

        List<Integer> list1 = new ArrayList<>();
        List<Integer> list2 = new ArrayList<>();
        List<Integer> list3 = new ArrayList<>();

        list1.add(1);
        list1.add(2);//Se añaden los elementos al final de la lista
        list1.add(6);
        list1.add(2, 5);//Se añade el 5 en la posición 2

        for (Integer i : list1) {//Recorremos la lista con un bucle for-each: 1 2 5 6
            System.out.printf(" %d ", i);
        }

        System.out.println();

        list2.add(3);
        list2.add(4);
        list1.addAll(2, list2);//Se inserta list2 en la posición 2 de list1

        for (Integer i : list1) {// 1 2 3 4 5 6
            System.out.printf(" %d ", i);
        }

        System.out.println();

        list3.add(7);
        list3.add(8);
        list1.addAll(list3);//Se inserta list3 al final de list1

        for (Integer i : list1) {// 1 2 3 4 5 6 7 8
            System.out.printf(" %d ", i);
        }

        System.out.printf("\nEl elemento 3 de list1 es: %d", list1.get(3));//4
        System.out.printf("\nLa posición del 4 en list1 es: %d", list1.indexOf(4));//3
        list1.add(4);//Se añade un 4 al final de list1

        System.out.printf("\nLa posición del 4 en list1 por el final es: %d\n", list1.lastIndexOf(4));//8

        list1.remove(8);//Se elimina el elemento de la posición 8, que es el último 4 insertado

        for (Integer i : list1) {// 1 2 3 4 5 6 7 8
            System.out.printf(" %d ", i);
        }

        list1.set(6, 8);//Se reemplaza el elemento que se encuentra en la posición 6 por un 8
        System.out.println();

        for (Integer i : list1) {// 1 2 3 4 5 6 8 8
            System.out.printf(" %d ", i);
        }

        System.out.printf("\nLa sub-lista comprendida entre las posiciones 2 y 5 es: ");
        for (Integer i : list1.subList(2, 6)) {// 3 4 5 6
            System.out.printf(" %d ", i);
        }

        System.out.printf("\nEl 4 %s se encuentra en list1", list1.contains(4) ? "sí" : "no");//sí
        System.out.printf("\nEl 9 %s se encuentra en list1", list1.contains(9) ? "sí" : "no");//no
        //list1: 1 2 3 4 5 6 8 8
        //list2: 3 4
        //list3: 7 8

        System.out.printf("\nTodos los elementos de list2 %s se encuentran en list1", list1.containsAll(list2) ? "sí" : "no");//sí

        System.out.printf("\nTodos los elementos de list3 %s se encuentran en list1\n",
        list1.containsAll(list3) ? "sí" : "no");//no

        list1.removeAll(list3);//Se eliminan de list1 todos los elementos de list3, es decir, el 8

        for (Integer i : list1) {// 1 2 3 4 5 6
            System.out.printf(" %d ", i);
        }

        System.out.println();
        list1.retainAll(list2);//Intersección entre list1 y list2

        for (Integer i : list1) {// 3 4
            System.out.printf(" %d ", i);
        }

        System.out.printf("\nEl tamaño de list1 es: %d", list1.size());//2
        System.out.printf("\nlist1 %s está vacía", list1.isEmpty() ? "sí" : "no");//no

        list1.clear();//Elimina todos los elementos de list1

        System.out.printf("\nlist1 %s está vacía", list1.isEmpty() ? "sí" : "no");//sí
    }

    public static void main(String[] args) {
        new ShowArrayList().show();
    }
}
1 2 5 6
1 2 3 4 5 6
1 2 3 4 5 6 7 8
El elemento 3 de list1 es: 4
La posición del 4 en list1 es: 3
La posición del 4 en list1 por el final es: 8
1 2 3 4 5 6 7 8
1 2 3 4 5 6 8 8
La sub-lista comprendida entre las posiciones 2 y 5 es: 3 4 5 6
El 4 sí se encuentra en list1
El 9 no se encuentra en list1
Todos los elementos de list2 sí se encuentran en list1
Todos los elementos de list3 no se encuentran en list1
1 2 3 4 5 6
3 4
El tamaño de list1 es: 2
list1 no está vacía
list1 sí está vacía

Iteradores

En diseño de software, el patrón de diseño Iterador (en inglés, Iterator) define una interfaz que declara los métodos necesarios para acceder secuencialmente a un grupo de objetos de una colección.

Este patrón debe ser utilizado cuando se requiera una forma estándar de recorrer una colección, es decir, cuando no sea necesario que un cliente sepa el tipo de colección que está recorriendo.

La interfaz Iterable<T> contiene el método iterator() que devuelve una instancia de alguno clase que implemente la interfaz Iterator<T>:

  • Iterator<T> iterator(): devuelve un iterador al comienzo de la colección.

La interfaz Iterator<E> permite el acceso secuencial a los elementos de una colección y realizar recorridos sobre la colección. Los métodos de Iterator<E> son:

  • boolean hasNext(): comprueba si hay siguiente elemento.
  • E next(): devuelve el siguiente elemento y mueve el iterador.
  • void remove(): se invoca después de next() para eliminar el último elemento leído.

La interfaz Collection<E> es una sub-interfaz o interfaz hija de Iterable<E>, así que dispone del método iterator(). Veamos un ejemplo de un ArrayList<E>utilizando iteradores:

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class ShowIterator{
    public void show(){
        Iterator<String> it;
        List<String> list = new ArrayList<>();

        list.add("Juan");
        list.add("Antonio");
        list.add("Jaime");
        list.add("Vicente");

        it = list.iterator();

        while(it.hasNext()){
            System.out.println(it.next());
        }
    }

    public static void main(String[] args){
        new ShowIterator().show();
    }
}
Juan
Antonio
Jaime
Vicente

Si no hay siguiente, next() lanza una excepción NoSuchElementException:

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class NextException {
    public void show() {
        Iterator<String> it;
        List<String> list = new ArrayList<>();

        list.add("Juan");
        it = list.iterator();
        System.out.println(it.next());
        System.out.println(it.next());//(1)!
    }

    public static void main(String[] args) {
        new NextException().show();
    }
}
  1. Se lanza en ejecución una excepción NoSuchElementException

Tal y como vemos en el ejemplo anterior, hay que comprobar si hay siguiente con un hashNext() para que el next() no lance la excepción.

No se puede modificar la colección dentro del bucle for-each porque se lanza en ejecución la excepción ConcurrentModificationException, ya que estamos recorriendo y modificando la lista a la vez:

import java.util.ArrayList;
import java.util.List;

public class ModifyInsideForEach {
    public void show() {
        List<String> list = new ArrayList<>();

        list.add("Juan");
        list.add("Antonio");
        list.add("Jaime");
        list.add("Vicente");

        for (String s : list) {//(1)!
            System.out.printf(" %s ", s);
            if (s.equals("Antonio")) {
                list.remove("Antonio");
            }
        }
    }

    public static void main(String[] args) {
        new ModifyInsideForEach().show();
    }
}
  1. Se lanza en ejecución la excepción ConcurrentModificationException

Para solucionarlo, podemos utilizar el método remove()de Iterator<E>. Si se modifica una colección mientras se recorre, los iteradores quedan invalidados, a excepción del método remove() de la interfaz Iterator<E>. El método remove() permite eliminar elementos de la colección siendo la única forma adecuada para eliminar elementos durante la iteración:

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class RemoveIterator {
    public void show() {
        String s;
        Iterator<String> it;
        List<String> list = new ArrayList<>();

        list.add("Juan");
        list.add("Antonio");
        list.add("Jaime");
        list.add("Vicente");
        it = list.iterator();

        while (it.hasNext()) {
            s = it.next();
            if (s.equals("Antonio")) {
                it.remove();
            } else {
                System.out.println(s);
            }
        }
    }

    public static void main(String[] args) {
        new RemoveIterator().show();
    }
}
Juan
Jaime
Vicente

Solo puede haber una invocación a remove() por cada invocación next(). Si no cumple, se lanza en ejecución una excepción IllegalStateException. Por ejemplo, imaginemos que tenemos una lista de personas y queremos eliminar a Antonio y a la persona que venga detrás. Si cuando encontramos a Antonio, hacemos dos remove() seguidos, entonces salta la excepción:

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class RemoveNext1 {
    public void show() {
        String s;
        Iterator<String> it;
        List<String> list = new ArrayList<>();

        list.add("Juan");
        list.add("Antonio");
        list.add("Jaime");
        list.add("Vicente");

        it = list.iterator();

        while (it.hasNext()) {
            s = it.next();
            if (s.equals("Antonio")) {
            it.remove();
            it.remove();//(1)!
            } else {
                System.out.println(s);
            }
        }
    }

    public static void main(String[] args) {
        new RemoveNext1().show();
    }
}
  1. Se lanza en ejecución la excepción IllegalStateException

Tendríamos que hacer otro next() para el que venga detrás de Antonio:

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class RemoveNext2 {
    public void show() {
        String s;
        Iterator<String> it;
        List<String> list = new ArrayList<>();

        list.add("Juan");
        list.add("Antonio");
        list.add("Jaime");
        list.add("Vicente");

        it = list.iterator();

        while (it.hasNext()) {
            s = it.next();
            if (s.equals("Antonio")) {
                it.remove();
                it.next();
                it.remove();
            } else {
                System.out.println(s);
            }
        }
    }

    public static void main(String[] args) {
        new RemoveNext2().show();
    }
}
Juan
Vicente

La interfaz ListIterator<E> es una subinterfaz o interfaz hija de Iterator<E>. Es un iterador para listas que permite al programador recorrer la lista hacia delante y hacia atrás, modificar la lista durante la iteración y obtener la posición actual del iterador en la lista.

Hereda los métodos de Iterador<E> y además aporta otros métodos nuevos:

  • void add(E e): inserta el elemento en la lista antes del elemento que sería devuelto por next(), si lo hubiera, y después del elemento que sería devuelto por previous(), si lo hubiera. Una llamada posterior a next() no se vería afectada y una llamada posterior a previous() devolvería el nuevo elemento.
  • boolean hasPrevious(): comprueba si hay un elemento anterior.
  • int nextIndex(): devuelve el índice del elemento que sería devuelto por una llamada a next(). El índice del primer elemento es 0.
  • E previous(): devuelve el elemento anterior de la lista y mueve la posición del cursor hacia atrás.
  • int previousIndex(): devuelve el índice del elemento que sería devuelto por una llamada a previous().
  • void set(E e): sustituye el último elemento devuelto por next() o previous() por elemento e.

En la interfaz List<E>, hay dos métodos para crear este iterador:

  • ListIterator<E> listIterator(): se coloca antes del primer elemento para que al hacer el primer next() se devuelva el primer elemento.
  • ListIterator<E> listIterator(int index): se coloca antes del elemento que se encuentra en la posición index para que al hacer un next() se devuelva dicho elemento. Para recorrer la lista al revés, hay que crearlo con el tamaño de la lista para que el primer previous() devuelva el último.
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
public class ShowListIterator {
    public void show() {
        ListIterator<Integer> it;
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(4);
        list.add(6);

        //Recorrido de la lista hacia atrás:
        it = list.listIterator(list.size());

        while (it.hasPrevious()) {
            System.out.printf(" %d ", it.previous());// 6 4 2 1
        }

        it = list.listIterator(1);//Se coloca antes del elemento que se encuentra en la posición 1

        System.out.printf("\n %d ", it.next());//2
        System.out.printf("\n %d ", it.nextIndex());//2
        System.out.printf("\n %d ", it.previousIndex());//1

        it.add(3);
        System.out.printf("\n %d ", it.next());//4 Una llamada posterior a next() no se ve afectada

        it.add(5);
        System.out.printf("\n %d ", it.previous());//5 Una llamada posterior a previous() devuelve el nuevo elemento

        System.out.printf("\n %d \n", it.next());//5
        it.set(7);//sustituye el último elemento devuelto por next() por 7

        for (Integer i : list) {
            System.out.printf(" %d ", i);//1 2 3 4 7 6
        }
    }

    public static void main(String[] args) {
        new ShowListIterator().show();
    }
}