¿Cómo crear una Lista mutable a partir de un array Java sin afectar al array original?

JavaBeginner
Practicar Ahora

Introducción

En la programación Java, trabajar con diferentes estructuras de datos es esencial para el desarrollo eficiente de programas. Los arrays (arreglos) y las Lists (listas) son dos estructuras de datos fundamentales que sirven para propósitos diferentes. Mientras que los arrays proporcionan almacenamiento de tamaño fijo, las Lists ofrecen flexibilidad con dimensionamiento dinámico y métodos utilitarios adicionales.

Este tutorial se centra en convertir arrays Java en Lists mutables sin modificar los datos del array original. Al final de este lab, comprenderá la relación entre arrays y Lists y aprenderá técnicas prácticas para convertir entre ellos manteniendo la integridad de los datos.

Comprender los Arrays y las Lists de Java

En este paso, exploraremos las diferencias básicas entre los arrays (arreglos) y las Lists (listas) de Java creando ejemplos de ambos. También examinaremos cómo acceder y mostrar sus elementos.

Creación de un Proyecto Java

Comencemos creando un archivo Java simple para trabajar:

  1. Abra la terminal y navegue al directorio del proyecto:

    cd ~/project
  2. Cree un nuevo directorio para nuestros archivos Java:

    mkdir -p src/main/java
    cd src/main/java
  3. Cree un nuevo archivo Java llamado ArrayListDemo.java usando el WebIDE. Haga clic en el icono del Explorador en el WebIDE, navegue a project/src/main/java, haga clic con el botón derecho y seleccione "Nuevo archivo". Nómbrelo ArrayListDemo.java.

  4. Agregue el siguiente código al archivo:

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

public class ArrayListDemo {
    public static void main(String[] args) {
        System.out.println("Java Arrays vs Lists Example");
        System.out.println("============================");

        // Creating an array of integers
        int[] numbersArray = {1, 2, 3, 4, 5};

        // Displaying array contents
        System.out.println("Array contents: " + Arrays.toString(numbersArray));

        // Accessing array elements by index
        System.out.println("Array element at index 2: " + numbersArray[2]);

        // Creating a List using ArrayList
        List<Integer> numbersList = new ArrayList<>();
        numbersList.add(1);
        numbersList.add(2);
        numbersList.add(3);
        numbersList.add(4);
        numbersList.add(5);

        // Displaying list contents
        System.out.println("List contents: " + numbersList);

        // Accessing list elements by index
        System.out.println("List element at index 2: " + numbersList.get(2));

        // Demonstrating List flexibility - adding a new element
        numbersList.add(6);
        System.out.println("List after adding element: " + numbersList);

        // Demonstrating List flexibility - removing an element
        numbersList.remove(1); // Removes element at index 1
        System.out.println("List after removing element at index 1: " + numbersList);
    }
}
  1. Compile y ejecute el programa Java:

    cd ~/project
    javac src/main/java/ArrayListDemo.java
    java -cp src/main/java ArrayListDemo

Debería ver una salida similar a esta:

Java Arrays vs Lists Example
============================
Array contents: [1, 2, 3, 4, 5]
Array element at index 2: 3
List contents: [1, 2, 3, 4, 5]
List element at index 2: 3
List after adding element: [1, 2, 3, 4, 5, 6]
List after removing element at index 1: [1, 3, 4, 5, 6]

Diferencias Clave entre Arrays y Lists

  1. Flexibilidad de tamaño:

    • Los arrays tienen un tamaño fijo que no se puede cambiar después de la creación
    • Las Lists pueden crecer o disminuir dinámicamente según sea necesario
  2. Operaciones disponibles:

    • Los arrays tienen una funcionalidad integrada limitada
    • Las Lists proporcionan numerosos métodos para agregar, eliminar y manipular elementos
  3. Restricciones de tipo:

    • Los arrays pueden almacenar primitivos u objetos
    • Las Lists solo pueden almacenar objetos (pero el autoboxing permite almacenar primitivos indirectamente)

Comprender estas diferencias le ayuda a elegir la estructura de datos correcta para sus necesidades específicas.

Conversión de Arrays a Lists

Ahora que entendemos los conceptos básicos de los arrays y las Lists, exploremos diferentes formas de convertir un array en una List. Nos centraremos particularmente en la creación de Lists mutables a partir de arrays.

Crear un Nuevo Archivo Java

  1. Cree un nuevo archivo Java llamado ArrayToListConversion.java en el mismo directorio:

    Desde el WebIDE, navegue a project/src/main/java, haga clic con el botón derecho y seleccione "Nuevo archivo". Nómbrelo ArrayToListConversion.java.

  2. Agregue el siguiente código al archivo:

import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class ArrayToListConversion {
    public static void main(String[] args) {
        System.out.println("Converting Arrays to Lists");
        System.out.println("=========================");

        // Create an array of Strings
        String[] fruitsArray = {"Apple", "Banana", "Cherry", "Date", "Elderberry"};
        System.out.println("Original array: " + Arrays.toString(fruitsArray));

        // Method 1: Using Arrays.asList() - Creates a fixed-size list
        System.out.println("\nMethod 1: Arrays.asList()");
        List<String> fruitsListFixed = Arrays.asList(fruitsArray);
        System.out.println("List created with Arrays.asList(): " + fruitsListFixed);

        // Try to modify the list
        System.out.println("Changing element at index 0 to 'Apricot'");
        fruitsListFixed.set(0, "Apricot");
        System.out.println("List after change: " + fruitsListFixed);
        System.out.println("Original array after List change: " + Arrays.toString(fruitsArray));

        // This will cause UnsupportedOperationException
        try {
            System.out.println("Trying to add a new element to the fixed-size list...");
            fruitsListFixed.add("Fig");
        } catch (UnsupportedOperationException e) {
            System.out.println("Error: Cannot add to fixed-size list!");
        }

        // Reset the array for next example
        fruitsArray[0] = "Apple";

        // Method 2: Using new ArrayList<>(Arrays.asList()) - Creates a mutable list
        System.out.println("\nMethod 2: new ArrayList<>(Arrays.asList())");
        List<String> fruitsList = new ArrayList<>(Arrays.asList(fruitsArray));
        System.out.println("List created with new ArrayList<>(Arrays.asList()): " + fruitsList);

        // Modify the list
        System.out.println("Adding 'Fig' to the list");
        fruitsList.add("Fig");
        System.out.println("List after adding 'Fig': " + fruitsList);
        System.out.println("Original array after List modification: " + Arrays.toString(fruitsArray));

        // Method 3: Using Stream API (Java 8+)
        System.out.println("\nMethod 3: Using Stream API");
        List<String> fruitsListStream = Arrays.stream(fruitsArray)
                                              .collect(Collectors.toList());
        System.out.println("List created with Stream API: " + fruitsListStream);

        // Modify the list
        System.out.println("Adding 'Grape' to the list");
        fruitsListStream.add("Grape");
        System.out.println("List after adding 'Grape': " + fruitsListStream);
        System.out.println("Original array after List modification: " + Arrays.toString(fruitsArray));
    }
}
  1. Compile y ejecute el programa:

    cd ~/project
    javac src/main/java/ArrayToListConversion.java
    java -cp src/main/java ArrayToListConversion

Debería ver una salida similar a esta:

Converting Arrays to Lists
=========================
Original array: [Apple, Banana, Cherry, Date, Elderberry]

Method 1: Arrays.asList()
List created with Arrays.asList(): [Apple, Banana, Cherry, Date, Elderberry]
Changing element at index 0 to 'Apricot'
List after change: [Apricot, Banana, Cherry, Date, Elderberry]
Original array after List change: [Apricot, Banana, Cherry, Date, Elderberry]
Trying to add a new element to the fixed-size list...
Error: Cannot add to fixed-size list!

Method 2: new ArrayList<>(Arrays.asList())
List created with new ArrayList<>(Arrays.asList()): [Apple, Banana, Cherry, Date, Elderberry]
Adding 'Fig' to the list
List after adding 'Fig': [Apple, Banana, Cherry, Date, Elderberry, Fig]
Original array after List modification: [Apple, Banana, Cherry, Date, Elderberry]

Method 3: Using Stream API
List created with Stream API: [Apple, Banana, Cherry, Date, Elderberry]
Adding 'Grape' to the list
List after adding 'Grape': [Apple, Banana, Cherry, Date, Elderberry, Grape]
Original array after List modification: [Apple, Banana, Cherry, Date, Elderberry]

Comprender los Diferentes Métodos de Conversión

  1. Arrays.asList():

    • Crea una lista de tamaño fijo respaldada por el array original
    • El tamaño de la lista no se puede cambiar (no se pueden agregar/eliminar elementos)
    • Los cambios en los elementos de la lista afectan al array original
  2. new ArrayList<>(Arrays.asList()):

    • Crea una nueva ArrayList que contiene todos los elementos del array
    • La lista es mutable (se pueden agregar/eliminar elementos)
    • Los cambios en la lista no afectan al array original
  3. Stream API (Java 8+):

    • Un enfoque más moderno que utiliza la programación funcional
    • Crea una lista completamente independiente
    • Ofrece flexibilidad para realizar transformaciones durante la conversión

Para los principiantes, el segundo método (new ArrayList<>(Arrays.asList())) es generalmente el más útil porque crea una lista completamente mutable sin afectar al array original.

Trabajar con Arrays de Tipos Primitivos

En los pasos anteriores, trabajamos con arrays de tipos de referencia (String). Sin embargo, los arrays de Java también pueden contener tipos primitivos como int, double, etc. Convertir arrays de tipos primitivos a Lists requiere pasos adicionales porque los genéricos de Java solo funcionan con tipos de referencia.

Creemos un nuevo ejemplo para demostrar este proceso.

Crear un Nuevo Archivo Java

  1. Cree un nuevo archivo Java llamado PrimitiveArrayToList.java en el mismo directorio:

    Desde el WebIDE, navegue a project/src/main/java, haga clic con el botón derecho y seleccione "Nuevo archivo". Nómbrelo PrimitiveArrayToList.java.

  2. Agregue el siguiente código al archivo:

import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class PrimitiveArrayToList {
    public static void main(String[] args) {
        System.out.println("Converting Primitive Arrays to Lists");
        System.out.println("===================================");

        // Create a primitive int array
        int[] numbersArray = {10, 20, 30, 40, 50};
        System.out.println("Original primitive array: " + Arrays.toString(numbersArray));

        // Method 1: Manual conversion
        System.out.println("\nMethod 1: Manual conversion");
        List<Integer> numbersList1 = new ArrayList<>();
        for (int number : numbersArray) {
            numbersList1.add(number); // Autoboxing converts int to Integer
        }
        System.out.println("List after manual conversion: " + numbersList1);

        // Method 2: Using Java 8 Streams
        System.out.println("\nMethod 2: Using Java 8 Streams");
        List<Integer> numbersList2 = Arrays.stream(numbersArray)
                                          .boxed() // Converts IntStream to Stream<Integer>
                                          .collect(Collectors.toList());
        System.out.println("List after stream conversion: " + numbersList2);

        // Modify the lists to demonstrate independence from the array
        System.out.println("\nModifying the lists:");
        numbersList1.add(60);
        numbersList1.set(0, 15);
        numbersList2.add(70);
        numbersList2.remove(0);

        System.out.println("List 1 after modifications: " + numbersList1);
        System.out.println("List 2 after modifications: " + numbersList2);
        System.out.println("Original array after List modifications: " + Arrays.toString(numbersArray));

        // Create and convert other primitive type arrays
        System.out.println("\nOther primitive type examples:");

        double[] doubleArray = {1.1, 2.2, 3.3, 4.4, 5.5};
        List<Double> doubleList = Arrays.stream(doubleArray)
                                      .boxed()
                                      .collect(Collectors.toList());
        System.out.println("Double array: " + Arrays.toString(doubleArray));
        System.out.println("Double list: " + doubleList);

        boolean[] boolArray = {true, false, true, true, false};
        List<Boolean> boolList = new ArrayList<>();
        for (boolean value : boolArray) {
            boolList.add(value);
        }
        System.out.println("Boolean array: " + Arrays.toString(boolArray));
        System.out.println("Boolean list: " + boolList);
    }
}
  1. Compile y ejecute el programa:

    cd ~/project
    javac src/main/java/PrimitiveArrayToList.java
    java -cp src/main/java PrimitiveArrayToList

Debería ver una salida similar a esta:

Converting Primitive Arrays to Lists
===================================
Original primitive array: [10, 20, 30, 40, 50]

Method 1: Manual conversion
List after manual conversion: [10, 20, 30, 40, 50]

Method 2: Using Java 8 Streams
List after stream conversion: [10, 20, 30, 40, 50]

Modifying the lists:
List 1 after modifications: [15, 20, 30, 40, 50, 60]
List 2 after modifications: [20, 30, 40, 50, 70]
Original array after List modifications: [10, 20, 30, 40, 50]

Other primitive type examples:
Double array: [1.1, 2.2, 3.3, 4.4, 5.5]
Double list: [1.1, 2.2, 3.3, 4.4, 5.5]
Boolean array: [true, false, true, true, false]
Boolean list: [true, false, true, true, false]

Comprender la Conversión de Arrays de Tipos Primitivos

Al trabajar con arrays de tipos primitivos, hay dos consideraciones clave:

  1. Autoboxing (Autoboxing): Java convierte automáticamente los valores primitivos a sus objetos de clase wrapper (envoltorio) cuando se agregan a las colecciones. Por ejemplo, int se convierte en Integer.

  2. Métodos de Boxing (Boxing) para Streams: Al usar streams con arrays de tipos primitivos, necesita llamar al método .boxed() para convertir los streams primitivos en streams de objetos.

El proceso de conversión crea Lists completamente nuevas que son independientes de los arrays originales. Esto significa:

  1. Modificar la List no afectará al array original
  2. Las Lists son completamente mutables (puede agregar, eliminar o cambiar elementos)
  3. Cada elemento en la List es un nuevo objeto (wrapper) que contiene el valor del array

Esta independencia es particularmente útil cuando necesita manipular los datos sin arriesgarse a cambios en el array original.

Aplicaciones Prácticas y Errores Comunes

Ahora que entendemos cómo convertir arrays a Lists mutables, exploremos algunas aplicaciones prácticas y errores comunes. Crearemos un ejemplo final que demuestra casos de uso del mundo real y cómo evitar errores comunes.

Crear un Nuevo Archivo Java

  1. Cree un nuevo archivo Java llamado ArrayListPractical.java en el mismo directorio:

    Desde el WebIDE, navegue a project/src/main/java, haga clic con el botón derecho y seleccione "Nuevo archivo". Nómbrelo ArrayListPractical.java.

  2. Agregue el siguiente código al archivo:

import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
import java.util.Collections;

public class ArrayListPractical {
    public static void main(String[] args) {
        System.out.println("Practical Applications and Common Pitfalls");
        System.out.println("========================================");

        // Use case 1: Creating a mutable list from a configuration array
        String[] defaultSettings = {"dark-mode", "auto-save", "notifications"};
        System.out.println("Default settings array: " + Arrays.toString(defaultSettings));

        // Create a mutable list of user settings starting with defaults
        List<String> userSettings = new ArrayList<>(Arrays.asList(defaultSettings));
        System.out.println("Initial user settings list: " + userSettings);

        // User can add or remove settings
        userSettings.add("sync-enabled");
        userSettings.remove("notifications");
        System.out.println("Modified user settings: " + userSettings);
        System.out.println("Original default settings (unchanged): " + Arrays.toString(defaultSettings));

        // Use case 2: Working with legacy APIs that return arrays
        System.out.println("\nUse case: Working with legacy APIs");

        // Simulate a legacy API that returns an array
        String[] legacyData = getLegacyData();
        System.out.println("Data from legacy API (array): " + Arrays.toString(legacyData));

        // Convert to a mutable list for modern processing
        List<String> modernData = new ArrayList<>(Arrays.asList(legacyData));

        // Process with modern methods
        modernData.removeIf(item -> item.startsWith("OLD_"));
        modernData.replaceAll(item -> item.toLowerCase());
        System.out.println("Processed data (list): " + modernData);

        // Common Pitfall 1: Forgetting that Arrays.asList creates a fixed-size list
        System.out.println("\nCommon Pitfall 1: Fixed-size list");
        Integer[] numberArray = {1, 2, 3, 4, 5};

        // This creates a fixed-size list
        List<Integer> wrongWay = Arrays.asList(numberArray);
        System.out.println("Fixed-size list: " + wrongWay);

        try {
            wrongWay.add(6); // This will fail
        } catch (UnsupportedOperationException e) {
            System.out.println("Error: Cannot add to fixed-size list!");
        }

        // Correct way
        List<Integer> rightWay = new ArrayList<>(Arrays.asList(numberArray));
        rightWay.add(6); // This works fine
        System.out.println("Mutable list: " + rightWay);

        // Common Pitfall 2: Array of primitives
        System.out.println("\nCommon Pitfall 2: Array of primitives");
        int[] primitiveArray = {10, 20, 30};

        // This won't compile: Arrays.asList(primitiveArray)
        // List<Integer> primitiveList = Arrays.asList(primitiveArray);

        // Correct ways
        List<Integer> primitiveList1 = new ArrayList<>();
        for (int value : primitiveArray) {
            primitiveList1.add(value);
        }

        List<Integer> primitiveList2 = Arrays.stream(primitiveArray)
                                            .boxed()
                                            .toList();

        System.out.println("Primitive array: " + Arrays.toString(primitiveArray));
        System.out.println("Converted list (loop method): " + primitiveList1);
        System.out.println("Converted list (stream method): " + primitiveList2);
    }

    // Simulate a legacy API that returns an array
    private static String[] getLegacyData() {
        return new String[] {"OLD_RECORD1", "ACTIVE_DATA", "OLD_RECORD2", "CURRENT_INFO"};
    }
}
  1. Compile y ejecute el programa:

    cd ~/project
    javac src/main/java/ArrayListPractical.java
    java -cp src/main/java ArrayListPractical

Debería ver una salida similar a esta:

Practical Applications and Common Pitfalls
========================================
Default settings array: [dark-mode, auto-save, notifications]
Initial user settings list: [dark-mode, auto-save, notifications]
Modified user settings: [dark-mode, auto-save, sync-enabled]
Original default settings (unchanged): [dark-mode, auto-save, notifications]

Use case: Working with legacy APIs
Data from legacy API (array): [OLD_RECORD1, ACTIVE_DATA, OLD_RECORD2, CURRENT_INFO]
Processed data (list): [active_data, current_info]

Common Pitfall 1: Fixed-size list
Fixed-size list: [1, 2, 3, 4, 5]
Error: Cannot add to fixed-size list!
Mutable list: [1, 2, 3, 4, 5, 6]

Common Pitfall 2: Array of primitives
Primitive array: [10, 20, 30]
Converted list (loop method): [10, 20, 30]
Converted list (stream method): [10, 20, 30]

Conclusiones Clave de las Aplicaciones Prácticas

  1. Gestión de la Configuración: Usar arrays para la configuración predeterminada y Lists para la configuración específica del usuario permite flexibilidad al tiempo que se conservan los valores predeterminados.

  2. Trabajar con APIs (Application Programming Interface, Interfaz de Programación de Aplicaciones) heredadas: Muchas APIs de Java más antiguas devuelven arrays, que puede convertir a Lists para aprovechar las características modernas de Java como las expresiones lambda y las referencias a métodos.

  3. Errores Comunes a Evitar:

    • Recuerde que Arrays.asList() crea una lista de tamaño fijo que no puede crecer ni disminuir
    • Los arrays de tipos primitivos no se pueden pasar directamente a Arrays.asList(): debe usar bucles o streams con boxing (envoltorio)
  4. Mejor Práctica: Para la mayoría de los casos de uso, el enfoque más seguro es usar new ArrayList<>(Arrays.asList(array)) para arrays de objetos y métodos de stream para arrays de tipos primitivos.

Al comprender estos patrones y errores, puede trabajar eficazmente con arrays y Lists en sus aplicaciones Java, eligiendo la estructura de datos y el método de conversión correctos para cada situación.

Resumen

En este laboratorio, ha aprendido a crear Lists mutables a partir de arrays de Java, preservando los datos del array original. Ha explorado:

  1. Las diferencias fundamentales entre los arrays y las Lists de Java, incluyendo su estructura, capacidades y casos de uso.

  2. Múltiples métodos para convertir arrays a Lists:

    • Usando Arrays.asList() para una vista de tamaño fijo del array
    • Usando new ArrayList<>(Arrays.asList()) para una List completamente mutable
    • Usando la API de Stream con .boxed() para arrays de tipos primitivos
  3. Consideraciones especiales para los arrays de tipos primitivos, que requieren boxing (envoltorio) explícito para convertir a Lists de objetos wrapper (envoltorio).

  4. Aplicaciones prácticas y errores comunes al trabajar con arrays y Lists, incluyendo la gestión de la configuración y la interacción con APIs heredadas.

Estas técnicas son valiosas en muchas aplicaciones Java del mundo real donde necesita trabajar con arrays y Lists, particularmente cuando necesita preservar los datos originales mientras crea colecciones flexibles y mutables para la manipulación. Comprender la relación entre los arrays y las Lists le permite escribir código Java más eficiente y mantenible.