Arreglos

📦 Arrays

Los arrays son las estructuras de datos más simples y se usan para almacenar listas de elementos, como cadenas, números, objetos y, literalmente, cualquier cosa.
Estos elementos se almacenan secuencialmente en memoria.

Por ejemplo, si asignamos un array de 5 enteros, estos se almacenan en memoria de la siguiente manera:

Array en memoria

Supongamos que la dirección del primer elemento en memoria es 100.

Como probablemente sepas, los enteros en Java ocupan 4 bytes de memoria.

Por lo tanto:

  • El segundo elemento estará en la posición 104.

  • El tercero en la posición 108.

  • Y así sucesivamente.


Acceso por índice

Por esta razón, buscar elementos en un array por su índice es rapidísimo.

Le damos un índice y el compilador sabe exactamente a qué posición de memoria acceder.

👉 La complejidad en tiempo de ejecución es \(O(1)\).

Acceso O(1)

Como el cálculo de la dirección de memoria es simple, no requiere bucles ni lógica adicional.

Si necesitas acceder rápidamente a una lista de elementos, los arrays son la estructura ideal.


Redimensionamiento

En Java (y muchos lenguajes), los arrays son estáticos:
su tamaño se define al crearlos y no puede cambiar después.

¿Qué ocurre si necesitamos más espacio?
Debemos crear un nuevo array más grande y copiar los elementos del anterior en él.

👉 Este proceso tiene una complejidad de \(O(n)\) porque debemos copiar todos los elementos.

Resize O(n)

Eliminación

Eliminar elementos también depende del caso:

  • Si borramos el último elemento, es muy sencillo: \(O(1)\).

  • Pero si borramos el primer elemento, todos los demás deben desplazarse a la izquierda.

👉 Esto implica una complejidad de \(O(n)\) en el peor de los casos.

Delete O(n)

En general, las operaciones básicas sobre arrays son:

  • Lookup: \(O(1)\)

  • Insert: \(O(n)\)

  • Delete: \(O(n)\)

Arrays en Java

Un ejemplo simple en Java sería:

public class Main {
    public static void main(String[] args) {
        // Declarar un array de tamaño 5
        int[] numbers = new int[5];

        // Asignar valores
        numbers[0] = 10;
        numbers[1] = 20;
        numbers[2] = 30;
        numbers[3] = 40;
        numbers[4] = 50;

        // Recorrer y mostrar
        for (int i = 0; i < numbers.length; i++) {
            System.out.println("Elemento en índice " + i + ": " + numbers[i]);
        }
    }
}

Dado que los arrays tienen un tamaño fijo, no funcionan bien cuando no sabemos cuántos elementos necesitaremos, o cuando debemos añadir/eliminar muchos elementos.

En esos casos, usaremos listas enlazadas, que veremos más adelante en el curso.

🗂️ Clase Arreglos

👉 Ejercicio:

Vas a implementar tu propia clase Arreglo en Java, que funcione como un arreglo dinámico.

Recuerda que a diferencia de los arreglos estáticos de Java, este podrá crecer automáticamente cuando insertes nuevos elementos y también permitirá eliminar valores en una posición específica.

Tu clase debe incluir los siguientes métodos:

  • insertar(int item): agrega un nuevo elemento al final del arreglo.

    Si el arreglo está lleno, crea uno más grande y copia los elementos.

  • eliminarEn(int indice): elimina el elemento en la posición indicada y desplaza los demás.

  • indiceDe(int item): devuelve el índice del elemento si existe, o -1 si no está en el arreglo.

  • imprimir(): muestra el contenido actual del arreglo en consola.

Prueba tu clase con un programa principal (Main) que:

  1. Cree un Arreglo de tamaño inicial 3.
  2. Inserte los valores 10, 20 y 30, e imprima el arreglo.
  3. Inserte el valor 40 (el arreglo debe crecer automáticamente) e imprima de nuevo.
  4. Elimine el último elemento e imprima.
  5. Busque el índice de 10 y de 100 e imprima el resultado.

🧮 Crear la clase

➕ Método: insertar()

➖ Método: eliminarEn()

🔍 Método: busqueda()

Arreglos dinámicos en Java: Vector vs ArrayList

Construir un arreglo dinámico “a mano” nos enseña la idea clave: cuando se llena el arreglo interno, se crea otro más grande y se copian los elementos.
Java ya trae dos clases que hacen eso por nosotros:

  • ArrayList (la opción moderna y preferida).

  • Vector (clase histórica/legacy, con sincronización integrada).

Ambas viven en java.util, almacenan datos en un arreglo interno y crecen automáticamente cuando hace falta.

Diferencias esenciales (rápidas)

  • Crecimiento de capacidad: ArrayList suele crecer ~50%; Vector crece 100% (o por incremento fijo si lo defines).

  • Concurrencia: Vector sincroniza todos sus métodos (añade sobrecosto); ArrayList no sincroniza (más rápido en un hilo).

    Si necesitas hilos, hoy se prefiere ArrayList + una estrategia de sincronización específica (por ejemplo Collections.synchronizedList(...) o CopyOnWriteArrayList).


Genéricos en ArrayList

ArrayList<Integer> indica que la lista guarda objetos Integer (el wrapper de int).
También puedes usar ArrayList<String>, ArrayList<Estudiante>, etc.


Tour esencial de ArrayList

1) Importar y crear

import java.util.ArrayList;

ArrayList<Integer> lista = new ArrayList<>(); // lista vacía
System.out.println(lista); // []
  • ArrayList<Integer>: lista de Integer (no de int).
  • new ArrayList<>(): construye con capacidad por defecto (crecerá sola).

2) Agregar elementos (add)

lista.add(10);
lista.add(20);
lista.add(30);
System.out.println(lista); // [10, 20, 30]

// Insertar en una posición específica (desplaza lo que había)
lista.add(1, 20);
System.out.println(lista); // [10, 20, 20, 30]
  • add(e) al final es O(1) amortizado (puede saltar a O(n) cuando toca crecer).
  • add(i, e) en el medio/inicio suele ser O(n) (hay corrimientos).

3) Eliminar por objeto vs por índice

// Quitar por objeto: elimina la PRIMERA ocurrencia de 20
boolean ok = lista.remove(Integer.valueOf(20));
System.out.println(ok + " | " + lista); // true | [10, 20, 30]

// Quitar por índice (posición 0 = primer elemento)
int eliminado = lista.remove(0);
System.out.println(eliminado + " | " + lista); // 10 | [20, 30]
  • Importante: remove(20) llamaría a la versión “por índice” (porque 20 es int). Para quitar el objeto 20, usa Integer.valueOf(20).

4) Buscar y consultar

lista.add(30); // [20, 30, 30]
System.out.println(lista.indexOf(30));     // 1  (primera ocurrencia)
System.out.println(lista.lastIndexOf(30)); // 2  (última ocurrencia)
System.out.println(lista.contains(20));    // true
System.out.println(lista.size());          // 3
System.out.println(lista.isEmpty());       // false
  • indexOf/lastIndexOf/contains recorren y comparan → O(n).

5) Convertir a arreglo (toArray)

import java.util.Arrays;

Integer[] arr = lista.toArray(new Integer[0]);
System.out.println(Arrays.toString(arr)); // [20, 30, 30]
  • Útil cuando una API exige arreglo en vez de lista.

6) Recorrer con for-each

for (Integer x : lista) {
    System.out.print(x + " ");
}
// Salida: 20 30 30
System.out.println();

Complejidad (Big-O) habitual

  • Acceso por índice: O(1)
  • add(e) al final (amortizado): O(1), con crecimientos puntuales O(n)
  • add(i, e) / remove(i) / remove(obj) / contains / indexOf: O(n)

Si haces muchas inserciones/eliminaciones en medio, quizá una lista enlazada sea mejor; para acceso por índice y uso general, ArrayList suele ganar.


Buenas prácticas rápidas

  • Usa ArrayList por defecto.

  • Si necesitas concurrencia:

    • Collections.synchronizedList(new ArrayList<>()) para algo sencillo, o
    • CopyOnWriteArrayList si hay muchas lecturas y pocas escrituras.
  • Si ya conoces el tamaño aproximado, puedes dar capacidad inicial: new ArrayList<>(capacidad).


Código completo (para copiar/pegar)

import java.util.ArrayList;
import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        // 1) Crear
        ArrayList<Integer> lista = new ArrayList<>();
        System.out.println("Inicio: " + lista); // []

        // 2) Agregar
        lista.add(10);
        lista.add(20);
        lista.add(30);
        System.out.println("Después de add: " + lista); // [10, 20, 30]

        // Insertar en índice 1
        lista.add(1, 20);
        System.out.println("Insertando en índice 1: " + lista); // [10, 20, 20, 30]

        // 3) Eliminar: por objeto vs por índice
        boolean ok = lista.remove(Integer.valueOf(20)); // quita primera ocurrencia de 20
        System.out.println("remove(obj 20): " + ok + " | " + lista); // true | [10, 20, 30]

        int eliminado = lista.remove(0); // quita el 10
        System.out.println("remove(índice 0): " + eliminado + " | " + lista); // [20, 30]

        // 4) Buscar/consultar
        lista.add(30); // [20, 30, 30]
        System.out.println("indexOf(30): " + lista.indexOf(30));       // 1
        System.out.println("lastIndexOf(30): " + lista.lastIndexOf(30)); // 2
        System.out.println("contains(20): " + lista.contains(20));     // true
        System.out.println("size(): " + lista.size());                 // 3
        System.out.println("isEmpty(): " + lista.isEmpty());           // false

        // 5) toArray
        Integer[] arr = lista.toArray(new Integer[0]);
        System.out.println("toArray -> " + Arrays.toString(arr)); // [20, 30, 30]

        // 6) Recorrer
        System.out.print("Recorriendo: ");
        for (Integer x : lista) System.out.print(x + " ");
        System.out.println(); // 20 30 30
    }
}

Vector (referencia breve)

import java.util.Vector;

public class DemoVector {
    public static void main(String[] args) {
        Vector<Integer> v = new Vector<>(); // sincronizado (legacy)
        v.add(1);
        v.add(2);
        System.out.println(v); // [1, 2]
    }
}

Conclusión: para cursos y proyectos actuales, ArrayList es la opción por defecto; si necesitas hilos, agrega sincronización explícita y moderna.