Original article: Java List Methods Tutorial – Util List API Example

Las listas son estructuras de datos comúnmente utilizadas en todos los lenguajes de programación.

En este tutorial vamos a investigar la API List de Java. Comenzaremos con las operaciones básicas y luego nos adentraremos en cosas más avanzadas (como una comparación de diferentes tipos de listas, como ArrayList y LinkedList).

También te daré algunas pautas para ayudarte a elegir la implementación de la lista que mejor se adapte a tu situación.

Aunque el conocimiento básico de Java es suficiente para seguir el tutorial, la última sección requiere estructuras de datos básicas (Array, LinkedList) y conocimiento de Big-O. Si no estás familiarizado con ellos, no dudes en omitir esa sección.

Definición de listas

Las listas son colecciones ordenadas de objetos. Son similares a las secuencias en matemáticas en ese sentido. Sin embargo, son diferentes a los conjuntos que no tienen un cierto orden.

Un par de cosas a tener en cuenta: las listas pueden tener elementos duplicados y nulos. Son tipos de referencia o de objeto y, como todos los objetos en Java, se almacenan en las pilas.

Una lista en Java es una interfaz y hay muchos tipos de listas que implementan esta interfaz.

ListHierarchy
Collection Hierarchy

Usaré ArrayList en los primeros ejemplos, porque es el tipo de lista más utilizado.

ArrayList es básicamente un arreglo de tamaño variable. Casi siempre, vas a querer usar ArrayList en lugar de arreglos regulares, ya que brindan muchos métodos útiles.

La única ventaja de un arreglo solía ser su tamaño fijo (al no asignar más espacio del que necesita). Pero las listas también admiten tamaños fijos ahora.

Cómo crear una lista en Java

Basta de charlas, empecemos por crear nuestra lista.

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

public class CreateArrayList {
    public static void main(String[] args) {
        ArrayList<Integer> list0 = new ArrayList<>();

        // Makes use of polymorphism
        List list = new ArrayList<Integer>();

        // Local variable with "var" keyword, Java 10
        var list2 = new ArrayList<Integer>();
    }
}

En los corchetes angulares (<>) especificamos el tipo de objetos que vamos a almacenar.

Tenga en cuenta que el tipo entre paréntesis debe ser un tipo de objeto y no un tipo primitivo. Por lo tanto, tenemos que usar envoltorios de objetos, clase Integer en lugar de int, Double en lugar de double, y así sucesivamente.

Hay muchas formas de crear una ArrayList, pero presenté tres formas comunes en el fragmento anterior.

La primera forma es creando el objeto a partir de la clase ArrayList concreta, especificando ArrayList en el lado izquierdo de la asignación.

El segundo fragmento de código hace uso del polimorfismo mediante el uso de la lista en el lado izquierdo. Esto hace que la asignación se acople libremente con la clase ArrayList y nos permite asignar otros tipos de listas y cambiar fácilmente a una implementación de Lista diferente.

La tercera forma es la forma Java 10 de crear variables locales haciendo uso de la palabra clave var. El compilador interpreta el tipo de variable comprobando el lado derecho.

Podemos ver que todas las asignaciones dan como resultado el mismo tipo:

System.out.println(list0.getClass());
System.out.println(list.getClass());
System.out.println(list2.getClass());

Output:

class java.util.ArrayList
class java.util.ArrayList
class java.util.ArrayList

También podemos especificar la capacidad inicial de la lista.

List list = new ArrayList<>(20);

Esto es útil porque cada vez que la lista se llena e intenta agregar otro elemento, la lista actual se copia en una nueva lista con el doble de capacidad que la lista anterior. Todo esto sucede detrás de escena.

Esta operación hace que nuestra complejidad sea O(n), por lo que queremos evitarla. La capacidad por defecto es 10, por lo que si sabes que vas a almacenar más elementos, debes especificar la capacidad inicial.

Cómo agregar y actualizar elementos de lista en Java

Para agregar elementos a la lista podemos usar el método add. También podemos especificar el índice del nuevo elemento, pero ten cuidado al hacerlo, ya que puedes generar una excepción IndexOutOfBoundsException.

import java.util.ArrayList;

public class AddElement {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("hello");
        list.add(1, "world");
        System.out.println(list);
    }
}

Resultado:

[hello, world]

Podemos usar el método set para actualizar un elemento.

list.set(1, "from the otherside");
System.out.println(list);

Resultado:

[hello, world]
[hello, from the otherside]

Cómo recuperar y eliminar elementos de lista en Java

Para recuperar un elemento de la lista, puedes usar el método get y proporcionar el índice del elemento que desea obtener.

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

public class GetElement {
    public static void main(String[] args) {
        List list = new ArrayList<String>();
        list.add("hello");
        list.add("freeCodeCamp");

        System.out.println(list.get(1));
    }
}

Resultado:

freeCodeCamp

La complejidad de esta operación en ArrayList es O(1) ya que utiliza un arrelgo de acceso aleatorio regular en segundo plano.

Para eliminar un elemento de ArrayList, se utiliza el método remove.

list.remove(0);

Esto elimina el elemento en el índice 0, que es "hola" en este ejemplo.

También podemos llamar al método remove con un elemento para buscarlo y eliminarlo. Ten en cuenta que solo elimina la primera aparición del elemento si está presente.

public static void main(String[] args) {
        List list = new ArrayList();
        list.add("hello");
        list.add("freeCodeCamp");
        list.add("freeCodeCamp");

        list.remove("freeCodeCamp");
        System.out.println(list);
    }

Resultado:

[hello, freeCodeCamp]

Para eliminar todas las ocurrencias, podemos usar el método removeAll de la misma manera.

Estos métodos están dentro de la interfaz de List, por lo que todas las implementaciones de List los tienen (ya sea ArrayList, LinkedList o Vector).

Cómo obtener la longitud de una lista en Java

Para obtener la longitud de una lista, o el número de elementos, podemos usar el método size().

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

public class GetSize {
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("Welcome");
        list.add("to my post");
        System.out.println(list.size());
    }
}

Resultado:

2

Listas bidimensionales en Java

Es posible crear listas bidimensionales, similares a los arreglos 2D.

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

Usamos esta sintaxis para crear una lista de listas, y cada lista interna almacena números enteros. Pero aún no hemos inicializado las listas internas. Necesitamos crearlos y ponerlas en esta lista nosotros mismos:

int numberOfLists = 3;
for (int i = 0; i < numberOfLists; i++) {
    listOfLists.add(new ArrayList<>());
}

Estoy inicializando mis listas internas y estoy agregando 3 listas en este caso. También puedo agregar listas más adelante si es necesario.

Ahora podemos agregar elementos a nuestras listas internas. Para agregar un elemento, primero debemos obtener la referencia a la lista interna.

Por ejemplo, digamos que queremos agregar un elemento a la primera lista. Necesitamos obtener la primera lista y luego agregarla.

listOfLists.get(0).add(1);
Adding elements to 2D list

Aquí hay un ejemplo para ti. Intenta adivinar el resultado del siguiente segmento de código:

public static void main(String[] args) {
        ArrayList<ArrayList<Integer>> listOfLists = new ArrayList<>();
        System.out.println(listOfLists);
        int numberOfLists = 3;
        for (int i = 0; i < numberOfLists; i++) {
            listOfLists.add(new ArrayList<>());
        }

        System.out.println(listOfLists);

        listOfLists.get(0).add(1);
        listOfLists.get(1).add(2);
        listOfLists.get(2).add(0,3);

        System.out.println(listOfLists);
}

Resultado:

[]
[[], [], []]
[[1], [2], [3]]

Ten en cuenta que es posible imprimir las listas directamente (a diferencia de los arreglos regulares) porque anulan el método ttoString().

Métodos útiles en Java

Existen otros métodos y atajos útiles que se utilizan con frecuencia. En esta sección quiero familiarizarte con algunos de ellos para que te resulte más fácil trabajar con listas.

Cómo crear una lista con elementos en Java

Es posible crear y completar la lista con algunos elementos en una sola línea. Hay dos maneras de hacer esto.

La siguiente es la forma de la vieja escuela:

public static void main(String[] args) {
        List<String> list = Arrays.asList(
                                "freeCodeCamp",
                                "let's",
                                "create");
 }

Debes tener cuidado con una cosa al usar este método: Arrays.asList devuelve una lista inmutable. Entonces, si intentas agregar o eliminar elementos después de crear el objeto, obtendrás una excepción UnsupportedOperationException.

Es posible que tengas la tentación de usar la palabra clave final para que la lista sea inmutable, pero no funcionará como se esperaba.

Simplemente, se asegura de que la referencia al objeto no cambie, no le importa lo que sucede dentro del objeto. Por lo que permite poner y quitar.

final List<String> list2 = new ArrayList<>();
list2.add("erinc.io is the best blog ever!");
System.out.println(list2);

Resultado:

[erinc.io is the best blog ever!]

Ahora veamos la forma moderna de hacerlo:

ArrayList<String> friends =  new ArrayList<>(List.of("Gulbike", "Sinem", "Mete"));

El método List.of se envió con Java 9. Este método también devuelve una lista inmutable, pero podemos pasarla al constructor ArrayList para crear una lista mutable con esos elementos. Podemos agregar y quitar elementos a esta lista sin ningún problema.

Cómo crear una lista con N copias de algún elemento en Java

Java proporciona un método llamado NCopies que es especialmente útil para la evaluación comparativa. Puedes llenar un arreglo con cualquier número de elementos en una sola línea.

public class NCopies {
    public static void main(String[] args) {
        List<String> list = Collections.nCopies(10, "HELLO");
        System.out.println(list);
    }
}

Resultado:

[HELLO, HELLO, HELLO, HELLO, HELLO, HELLO, HELLO, HELLO, HELLO, HELLO]

Cómo clonar una lista en Java

Como se mencionó anteriormente, las Listas son tipos de referencia, por lo que se les aplican las reglas de paso por referencia.

public static void main(String[] args) {
        List list1 = new ArrayList<String>();
        list1.add("Hello");
        List list2 = list1;
        list2.add(" World");

        System.out.println(list1);
        System.out.println(list2);
}

Resultado:

[Hello,  World]
[Hello,  World]

La variable list1 contiene una referencia a la lista. Cuando lo asignamos a list2 también apunta al mismo objeto. Si no queremos que cambie la lista original, podemos clonar la lista.

ArrayList list3 = (ArrayList) list1.clone();
list3.add(" Of Java");

System.out.println(list1);
System.out.println(list3);

Resultado:

[Hello,  World]
[Hello,  World,  Of Java]

Dado que clonamos list1, list3 contiene una referencia a su clon en este caso. Por lo tanto, list1 permanece sin cambios.

Cómo copiar una lista a un arreglo en Java

A veces necesita convertir su lista en un arreglo para pasarla a un método que acepte un arreglo. Puedes usar el siguiente código para lograrlo:

List<Integer> list = new ArrayList<>(List.of(1, 2));
Integer[] toArray = list.toArray(new Integer[0]);

Debe pasar un arreglo y el método toArray  devuelve ese arreglo después de llenarla con los elementos de la lista.

Cómo ordenar una lista en Java

Para ordenar una lista podemos usar Collections.sort. Ordena en orden ascendente de forma predeterminada, pero también puedes pasar un comparador para ordenar con lógica personalizada.

List<Integer> toBeSorted = new ArrayList<>(List.of(3,2,4,1,-2));
Collections.sort(toBeSorted);
System.out.println(toBeSorted);

Resultado:

[-2, 1, 2, 3, 4]

Cómo elejir qué tipo de lista usar?

Antes de terminar este artículo, quiero brindarte una breve comparación de rendimiento de diferentes implementaciones de listas para que puedas elegir cuál es mejor para tu caso de uso.

Vamos a comparar ArrayList, LinkedList y Vector. Todos ellos tienen sus altibajos, así que asegúrate de considerar el contexto específico antes de decidir.

Java ArrayList vs LinkedList

Aquí hay una comparación de tiempos de ejecución en términos de complejidad algorítmica.

|                       | ArrayList                  | LinkedList |
|-----------------------|----------------------------|------------|
| GET(index)            | O(1)                       | O(n)       |
| GET from Start or End | O(1)                       | O(1)       |
| ADD                   | O(1), if list is full O(n) | O(1)       |
| ADD(index)            | O(n)                       | O(1)       |
| Remove(index)         | O(n)                       | O(1)       |
| Search and Remove     | O(n)                       | O(n)       |

Generally, the get operation is much faster on ArrayList but add and remove are faster on LinkedList.

En general, la operación get (obtener) es mucho más rápida en ArrayList, pero add (agregar) y remove (eliminar) son más rápidos en LinkedList.

ArrayList usa un arreglo detrás de escena, y cada vez que se elimina un elemento, los elementos de el arreglo deben cambiarse (que es una operación O (n)).

Elegir estructuras de datos es una tarea compleja y no existe una receta que se aplique a todas las situaciones. Aún así, intentaré proporcionar algunas pautas para ayudarte a tomar esa decisión más fácilmente:

  • Si planeas hacer más operaciones de obtener y agregar que no sean eliminar, usa ArrayList ya que la operación de obtener es demasiado costosa en LinkedList. Tenga en cuenta que la inserción es O(1) solo si la llamas sin especificar el índice y la agrega al final de la lista.
  • Si vas a eliminar elementos y/o insertarlos en el medio (no al final) con frecuencia, puedes considerar cambiar a LinkedList porque estas operaciones son costosas en ArrayList.
  • Ten en cuenta que si accedes a los elementos secuencialmente (con un iterador), no experimentarás una pérdida de rendimiento con LinkedList mientras obtienes elementos.
image-72
Benchmark source: programcreek

Java ArrayList vs Vector

Vector es muy similar a ArrayList. Si provienes de un entorno de C++, es posible que tengas la tentación de usar un Vector, pero su caso de uso es un poco diferente al de C++.

Los métodos de Vector tienen la palabra clave sincronizada, por lo que Vector garantiza la seguridad de subprocesos, mientras que ArrayList no.

Es posible que prefieras Vector sobre ArrayList en la programación de subprocesos múltiples o puedes usar ArrayList y manejar la sincronización usted mismo.

En un programa de subproceso único, es mejor quedarse con ArrayList porque la seguridad de subprocesos tiene un costo de rendimiento.

Conclusión

En esta publicación, he tratado de proporcionar una descripción general de la API de lista de Java. Hemos aprendido a usar métodos básicos y también hemos visto algunos trucos más avanzados para hacernos la vida más fácil.

También hicimos una comparación de ArrayList, LinkedList y Vector, que es un tema que se suele preguntar en las entrevistas.

Gracias por tomarse el tiempo de leer el artículo completo y espero que te  haya sido útil.

Puedes acceder a todo el código desde este repositorio.

Si estás interesado en leer más artículos como este, puede suscribirte a la lista de correo de mi blog para recibir una notificación cuando publique un nuevo artículo.