配列と文字列

1. 配列の基本

配列は同じ型の複数の値を格納するためのデータ構造です。 Javaでは配列はオブジェクトとして扱われ、一度作成したら大きさを変更できません。

配列の宣言と初期化


// 宣言のみ(この時点では配列は作成されていない)
int[] numbers;
String[] names;

// 宣言と同時に初期化(new演算子で配列を作成)
int[] scores = new int[5]; // 5つの要素を持つint型配列(初期値は0)
String[] fruits = new String[3]; // 3つの要素を持つString型配列(初期値はnull)

// 宣言と同時に初期化(初期値を指定)
int[] primes = {2, 3, 5, 7, 11};
String[] colors = {"赤", "青", "緑"};
            

配列の初期値

配列を作成すると、各要素は型に応じた初期値で自動的に初期化されます:

  • 数値型(byte, short, int, long, float, double): 0
  • char型: '\u0000'(NUL文字)
  • boolean型: false
  • 参照型(String, クラス型など): null

2. 配列の操作

配列要素へのアクセス

配列の要素にはインデックス(添え字)を使ってアクセスします。 インデックスは0から始まります。


int[] numbers = {10, 20, 30, 40, 50};

// 要素へのアクセス
int firstElement = numbers[0]; // 10
int thirdElement = numbers[2]; // 30

// 要素の変更
numbers[1] = 25; // 2番目の要素を25に変更

// 配列の長さを取得
int length = numbers.length; // 5
            

配列の境界チェック

存在しないインデックスにアクセスすると、ArrayIndexOutOfBoundsExceptionが発生します。


int[] arr = {1, 2, 3};
int value = arr[3]; // 例外発生!インデックスは0〜2まで
            

配列の繰り返し処理

配列の全要素に対して処理を行うには、いくつかの方法があります。


String[] fruits = {"りんご", "バナナ", "オレンジ"};

// 従来のfor文
for (int i = 0; i < fruits.length; i++) {
    System.out.println(fruits[i]);
}

// 拡張for文(for-each)- Java 5以降
for (String fruit : fruits) {
    System.out.println(fruit);
}

// Java 8以降のStream API
Arrays.stream(fruits).forEach(System.out::println);
            

配列のコピー

配列をコピーするには複数の方法があります。


int[] original = {1, 2, 3, 4, 5};

// 方法1: System.arraycopyメソッド
int[] copy1 = new int[original.length];
System.arraycopy(original, 0, copy1, 0, original.length);

// 方法2: Arrays.copyOfメソッド
int[] copy2 = Arrays.copyOf(original, original.length);

// 方法3: clone()メソッド
int[] copy3 = original.clone();
            

浅いコピーと深いコピー

上記のコピー方法はすべて「浅いコピー」です。 つまり、配列の要素が参照型の場合、参照先のオブジェクトはコピーされません。 オブジェクトの配列を完全にコピーするには、各要素を個別にコピーする「深いコピー」が必要です。

配列のソートと検索

java.util.Arraysクラスには、配列を操作するための便利なメソッドが用意されています。


import java.util.Arrays;

// 配列のソート
int[] numbers = {5, 2, 9, 1, 3};
Arrays.sort(numbers); // [1, 2, 3, 5, 9]

// 配列の検索(二分探索 - ソート済みの配列が必要)
int index = Arrays.binarySearch(numbers, 3); // 2(3の位置)

// 配列の比較
int[] array1 = {1, 2, 3};
int[] array2 = {1, 2, 3};
boolean areEqual = Arrays.equals(array1, array2); // true

// 配列の文字列表現
String arrayAsString = Arrays.toString(numbers); // "[1, 2, 3, 5, 9]"
            

3. 多次元配列

Javaでは、配列の配列として多次元配列を表現します。 最も一般的なのは2次元配列です。

2次元配列の宣言と初期化


// 宣言と初期化
int[][] matrix = new int[3][4]; // 3行4列の2次元配列

// 初期値を指定して初期化
int[][] grid = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};
            

不規則な多次元配列

Javaでは、各行の長さが異なる「ジャグ配列」も作成できます。


// 各行の長さが異なる2次元配列
int[][] jaggedArray = new int[3][];
jaggedArray[0] = new int[2];
jaggedArray[1] = new int[4];
jaggedArray[2] = new int[3];

// 初期値を指定
int[][] triangle = {
    {1},
    {2, 3},
    {4, 5, 6}
};
            

多次元配列の操作


int[][] matrix = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

// 要素へのアクセス
int value = matrix[1][2]; // 6(2行目、3列目)

// 行数と列数の取得
int rows = matrix.length; // 3(行数)
int columns = matrix[0].length; // 3(最初の行の列数)

// 2次元配列の繰り返し処理
for (int i = 0; i < matrix.length; i++) {
    for (int j = 0; j < matrix[i].length; j++) {
        System.out.print(matrix[i][j] + " ");
    }
    System.out.println();
}

// 拡張for文を使用
for (int[] row : matrix) {
    for (int element : row) {
        System.out.print(element + " ");
    }
    System.out.println();
}
            

4. 文字列(String)の基本

Javaでは、文字列はStringクラスのオブジェクトとして表現されます。 Stringは不変(immutable)なので、一度作成すると内容を変更できません。

文字列の作成


// リテラルを使用した作成
String name = "Java";

// コンストラクタを使用した作成
String language = new String("Java");

// 文字配列からの作成
char[] chars = {'H', 'e', 'l', 'l', 'o'};
String greeting = new String(chars);
            

文字列リテラルとString Pool

文字列リテラル(ダブルクォートで囲まれた文字列)は、JVMの「String Pool」と呼ばれる特別な領域に保存されます。 同じ内容の文字列リテラルは同じオブジェクトを参照します。 一方、newキーワードで作成した文字列は常に新しいオブジェクトになります。


String s1 = "Hello";
String s2 = "Hello";
String s3 = new String("Hello");

System.out.println(s1 == s2); // true(同じオブジェクトを参照)
System.out.println(s1 == s3); // false(異なるオブジェクト)
System.out.println(s1.equals(s3)); // true(内容は同じ)
            

文字列の不変性

Stringオブジェクトは不変です。 文字列を操作するメソッドは、元の文字列を変更するのではなく、新しい文字列を返します。


String original = "Hello";
String modified = original.concat(" World"); // 新しい文字列を作成

System.out.println(original); // "Hello"(変更されていない)
System.out.println(modified); // "Hello World"(新しい文字列)
            

5. 文字列操作メソッド

Stringクラスには、文字列を操作するための多数のメソッドが用意されています。

基本的な文字列操作


String text = "Hello, Java World!";

// 長さの取得
int length = text.length(); // 18

// 文字の取得
char firstChar = text.charAt(0); // 'H'

// 部分文字列の取得
String sub1 = text.substring(7); // "Java World!"
String sub2 = text.substring(7, 11); // "Java"

// 文字列の結合
String combined = text.concat(" Welcome!"); // "Hello, Java World! Welcome!"
// または + 演算子を使用
String combined2 = text + " Welcome!"; // 同じ結果
            

検索と比較


String text = "Hello, Java World!";

// 文字/部分文字列の検索
int index1 = text.indexOf('J'); // 7
int index2 = text.indexOf("Java"); // 7
int index3 = text.lastIndexOf('o'); // 15(最後の 'o' の位置)

// 文字列の比較
boolean equals = text.equals("Hello, Java World!"); // true
boolean equalsIgnoreCase = text.equalsIgnoreCase("hello, java world!"); // true

// 文字列の比較(辞書順)
int comp1 = "apple".compareTo("banana"); // 負の値(apple は banana より前)
int comp2 = "banana".compareTo("apple"); // 正の値(banana は apple より後)
            

変換と置換


String text = "Hello, Java World!";

// 大文字・小文字変換
String upper = text.toUpperCase(); // "HELLO, JAVA WORLD!"
String lower = text.toLowerCase(); // "hello, java world!"

// 置換
String replaced = text.replace('o', 'O'); // "HellO, Java WOrld!"
String replacedAll = text.replaceAll("Java", "Python"); // "Hello, Python World!"

// 空白の除去
String withSpaces = "  Hello  ";
String trimmed = withSpaces.trim(); // "Hello"

// 文字列の分割
String csv = "apple,banana,orange";
String[] fruits = csv.split(","); // ["apple", "banana", "orange"]
            

文字列の判定


// 空文字列かどうか
boolean isEmpty = "".isEmpty(); // true
boolean isNotEmpty = "Hello".isEmpty(); // false

// Java 11以降: 空白文字のみかどうか
boolean isBlank = "   ".isBlank(); // true
boolean isNotBlank = "Hello".isBlank(); // false

// 特定の文字/文字列で始まるか終わるか
boolean startsWith = "Hello".startsWith("He"); // true
boolean endsWith = "Hello".endsWith("lo"); // true

// 特定の文字/文字列を含むか
boolean contains = "Hello".contains("ell"); // true
            

6. StringBuilder と StringBuffer

文字列の連結や変更を頻繁に行う場合、Stringクラスの不変性はパフォーマンス上の問題になります。 そのような場合は、可変の文字列を扱うStringBuilderまたはStringBufferを使用します。

StringBuilder

StringBuilderは単一スレッドでの使用に適しています。 同期化されていないため、StringBufferより高速です。


// StringBuilder の作成
StringBuilder sb = new StringBuilder();
StringBuilder sb2 = new StringBuilder("Hello");

// 文字列の追加
sb.append("Hello");
sb.append(" ");
sb.append("World");
System.out.println(sb.toString()); // "Hello World"

// メソッドチェーン
StringBuilder sb3 = new StringBuilder();
sb3.append("Java").append(" is").append(" awesome!");
System.out.println(sb3.toString()); // "Java is awesome!"

// 挿入
sb.insert(5, ","); // "Hello, World"

// 削除
sb.delete(5, 7); // "Hello World"(5から6までの文字を削除)

// 置換
sb.replace(6, 11, "Java"); // "Hello Java"

// 反転
sb.reverse(); // "avaJ olleH"
            

StringBuffer

StringBufferはStringBuilderと同じメソッドを持ちますが、スレッドセーフです。 複数のスレッドから同時にアクセスされる可能性がある場合に使用します。


// StringBuffer の作成と操作(メソッドはStringBuilderと同じ)
StringBuffer buffer = new StringBuffer("Hello");
buffer.append(" World");
System.out.println(buffer.toString()); // "Hello World"
            

StringBuilder vs StringBuffer

特性 StringBuilder StringBuffer
可変性 可変 可変
スレッドセーフ ×
パフォーマンス 高速 やや遅い
導入バージョン Java 5 Java 1.0
推奨用途 単一スレッド環境 マルチスレッド環境

7. 文字列操作のパフォーマンス

文字列操作のパフォーマンスは、アプリケーションの効率に大きな影響を与えることがあります。

文字列連結のパフォーマンス比較


// 1. String連結(非効率)
String result1 = "";
for (int i = 0; i < 10000; i++) {
    result1 += i; // 毎回新しいStringオブジェクトが作成される
}

// 2. StringBuilder(効率的)
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
    sb.append(i);
}
String result2 = sb.toString();

// 3. StringBuffer(スレッドセーフだが、やや遅い)
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < 10000; i++) {
    buffer.append(i);
}
String result3 = buffer.toString();
            

パフォーマンスのヒント

  • ループ内での文字列連結には、StringBuilderを使用する
  • StringBuilderの初期容量を適切に設定する(大量の文字列を追加する場合)
  • 不必要な一時的な文字列オブジェクトの作成を避ける
  • マルチスレッド環境でない限り、StringBufferよりStringBuilderを優先する

文字列プールの活用


// 文字列リテラルはプールされる
String s1 = "Hello";
String s2 = "Hello"; // s1と同じオブジェクトを参照

// newで作成した文字列はプールされない
String s3 = new String("Hello"); // 新しいオブジェクト

// internメソッドでプールに追加
String s4 = s3.intern(); // プール内の"Hello"を参照
System.out.println(s1 == s4); // true
            

8. 練習問題

問題1: 配列の最大値

整数の配列から最大値を見つけるメソッドを作成してください。


public static int findMax(int[] array) {
    // ここにコードを書く
}
              

問題2: 文字列の逆転

与えられた文字列を逆順にするメソッドを作成してください。StringBuilderを使用してください。


public static String reverse(String str) {
    // ここにコードを書く
}
              

問題3: 回文チェック

与えられた文字列が回文(前から読んでも後ろから読んでも同じ)かどうかを判定するメソッドを作成してください。大文字小文字は区別せず、空白や記号は無視してください。


public static boolean isPalindrome(String str) {
    // ここにコードを書く
}