Java 配列から元の配列に影響を与えずに可変リストを作成する方法

JavaBeginner
オンラインで実践に進む

はじめに

Java プログラミングにおいて、効率的なプログラム開発には、様々なデータ構造を扱うことが不可欠です。配列とリストは、異なる目的に役立つ 2 つの基本的なデータ構造です。配列は固定サイズのストレージを提供しますが、リストは動的なサイズ変更と追加のユーティリティメソッドによる柔軟性を提供します。

このチュートリアルでは、元の配列データを変更することなく、Java の配列を可変リストに変換することに焦点を当てます。この実験(Lab)の終わりには、配列とリストの関係を理解し、データ整合性を維持しながら、それらの間で変換するための実践的なテクニックを習得できます。

Java の配列とリストの理解

このステップでは、Java の配列とリストの基本的な違いを、両方の例を作成して探求します。また、それらの要素へのアクセスと表示方法も調べます。

Java プロジェクトの作成

まず、作業用のシンプルな Java ファイルを作成しましょう。

  1. ターミナルを開き、プロジェクトディレクトリに移動します。

    cd ~/project
  2. Java ファイル用の新しいディレクトリを作成します。

    mkdir -p src/main/java
    cd src/main/java
  3. WebIDE を使用して、ArrayListDemo.javaという新しい Java ファイルを作成します。WebIDE の Explorer アイコンをクリックし、project/src/main/javaに移動し、右クリックして「New File」を選択します。ファイル名をArrayListDemo.javaとします。

  4. ファイルに以下のコードを追加します。

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. Java プログラムをコンパイルして実行します。
    cd ~/project
    javac src/main/java/ArrayListDemo.java
    java -cp src/main/java ArrayListDemo

次のような出力が表示されるはずです。

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]

配列とリストの主な違い

  1. サイズの柔軟性:

    • 配列は、作成後に変更できない固定サイズを持ちます。
    • リストは、必要に応じて動的に拡大または縮小できます。
  2. 利用可能な操作:

    • 配列には、組み込み機能が限られています。
    • リストは、要素の追加、削除、および操作のための多数のメソッドを提供します。
  3. 型の制約:

    • 配列はプリミティブまたはオブジェクトを格納できます。
    • リストはオブジェクトのみを格納できます(ただし、オートボクシングにより、プリミティブを間接的に格納できます)。

これらの違いを理解することで、特定のニーズに合った適切なデータ構造を選択できます。

配列からリストへの変換

配列とリストの基本を理解したところで、配列をリストに変換するさまざまな方法を探ってみましょう。特に、配列から可変リストを作成することに焦点を当てます。

新しい Java ファイルの作成

  1. 同じディレクトリに、ArrayToListConversion.javaという新しい Java ファイルを作成します。

    WebIDE から、project/src/main/javaに移動し、右クリックして「New File」を選択します。ファイル名をArrayToListConversion.javaとします。

  2. ファイルに以下のコードを追加します。

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. プログラムをコンパイルして実行します。
    cd ~/project
    javac src/main/java/ArrayToListConversion.java
    java -cp src/main/java ArrayToListConversion

次のような出力が表示されるはずです。

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]

さまざまな変換方法の理解

  1. Arrays.asList():

    • 元の配列を基にした固定サイズのリストを作成します。
    • リストのサイズは変更できません(要素の追加/削除はできません)。
    • リスト要素への変更は、元の配列に影響します。
  2. new ArrayList<>(Arrays.asList()):

    • 配列のすべての要素を含む新しい ArrayList を作成します。
    • リストは可変です(要素を追加/削除できます)。
    • リストへの変更は、元の配列に影響しません。
  3. Stream API (Java 8+):

    • 関数型プログラミングを使用した、よりモダンなアプローチです。
    • 完全に独立したリストを作成します。
    • 変換中に変換を実行する柔軟性を提供します。

初心者にとって、2 番目の方法(new ArrayList<>(Arrays.asList()))は、元の配列に影響を与えることなく、完全に可変なリストを作成するため、一般的に最も役立ちます。

プリミティブ配列の操作

前のステップでは、参照型(String)の配列を扱いました。しかし、Java の配列には、intdoubleなどのプリミティブ型も格納できます。プリミティブ配列をリストに変換するには、Java のジェネリクスが参照型でのみ機能するため、追加の手順が必要です。

このプロセスを実演するために、新しい例を作成しましょう。

新しい Java ファイルの作成

  1. 同じディレクトリに、PrimitiveArrayToList.javaという新しい Java ファイルを作成します。

    WebIDE から、project/src/main/javaに移動し、右クリックして「New File」を選択します。ファイル名をPrimitiveArrayToList.javaとします。

  2. ファイルに以下のコードを追加します。

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. プログラムをコンパイルして実行します。
    cd ~/project
    javac src/main/java/PrimitiveArrayToList.java
    java -cp src/main/java PrimitiveArrayToList

次のような出力が表示されるはずです。

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]

プリミティブ配列の変換の理解

プリミティブ配列を扱う際には、2 つの重要な考慮事項があります。

  1. オートボクシング(Autoboxing): Java は、コレクションに追加する際に、プリミティブ値をラッパークラスオブジェクトに自動的に変換します。たとえば、intIntegerに変換されます。

  2. ストリームのボクシングメソッド(Boxing Methods for Streams): プリミティブ配列でストリームを使用する場合は、プリミティブストリームをオブジェクトストリームに変換するために、.boxed()メソッドを呼び出す必要があります。

変換プロセスは、元の配列から完全に独立した新しいリストを作成します。これは、次のことを意味します。

  1. リストを変更しても、元の配列には影響しません。
  2. リストは完全に可変です(要素を追加、削除、または変更できます)。
  3. リスト内の各要素は、配列からの値を含む新しいオブジェクト(ラッパー)です。

この独立性は、元の配列への変更をリスクにさらすことなく、データを操作する必要がある場合に特に役立ちます。

実用的なアプリケーションとよくある落とし穴

配列を可変リストに変換する方法を理解したところで、いくつかの実用的なアプリケーションとよくある落とし穴を探ってみましょう。実際のユースケースと、よくある間違いを回避する方法を示す最終的な例を作成します。

新しい Java ファイルの作成

  1. 同じディレクトリに、ArrayListPractical.javaという新しい Java ファイルを作成します。

    WebIDE から、project/src/main/javaに移動し、右クリックして「New File」を選択します。ファイル名をArrayListPractical.javaとします。

  2. ファイルに以下のコードを追加します。

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. プログラムをコンパイルして実行します。
    cd ~/project
    javac src/main/java/ArrayListPractical.java
    java -cp src/main/java ArrayListPractical

次のような出力が表示されるはずです。

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]

実用的なアプリケーションからの重要なポイント

  1. 構成管理: デフォルト設定に配列を使用し、ユーザー固有の設定にリストを使用することで、デフォルトを保持しながら柔軟性を確保できます。

  2. レガシーAPI との連携: 多くの古い Java API は配列を返します。これをリストに変換することで、ラムダ式やメソッド参照などの最新の Java 機能を活用できます。

  3. 避けるべき一般的な落とし穴:

    • Arrays.asList()は、サイズを変更できない固定サイズのリストを作成することに注意してください。
    • プリミティブ配列は、Arrays.asList()に直接渡すことはできません。ループまたはボクシングを使用したストリームを使用する必要があります。
  4. ベストプラクティス: ほとんどのユースケースでは、オブジェクト配列にはnew ArrayList<>(Arrays.asList(array))を使用し、プリミティブ配列にはストリームメソッドを使用することが最も安全なアプローチです。

これらのパターンと落とし穴を理解することで、Java アプリケーションで配列とリストの両方を効果的に操作し、各状況に適したデータ構造と変換方法を選択できます。

まとめ

この実験では、元の配列データを保持しながら、Java 配列から可変リストを作成する方法を学びました。以下の内容を探求しました。

  1. Java 配列とリストの基本的な違い。構造、機能、ユースケースなど。

  2. 配列をリストに変換する複数の方法:

    • 配列の固定サイズビューにArrays.asList()を使用する
    • 完全な可変リストにnew ArrayList<>(Arrays.asList())を使用する
    • プリミティブ配列に.boxed()を使用した Stream API を使用する
  3. プリミティブ配列に関する特別な考慮事項。ラッパーオブジェクトのリストに変換するには、明示的なボクシングが必要です。

  4. 配列とリストを扱う際の、実用的なアプリケーションとよくある落とし穴。構成管理やレガシーAPI との連携など。

これらのテクニックは、配列とリストの両方を扱う必要のある多くの現実世界の Java アプリケーションで役立ちます。特に、操作のために柔軟で可変なコレクションを作成しながら、元のデータを保持する必要がある場合に有効です。配列とリストの関係を理解することで、より効率的で保守性の高い Java コードを作成できます。