Java String クラスの不可変性と主要メソッドの詳解

Java の文字列クラス(String)概要

Java において、テキストデータを扱う中心的なクラスは java.lang.String です。このクラスは言語レベルでサポートされており、以下の重要な特性を持っています。

  • パッケージ:java.lang (自動インポートされます)
  • 実装インターフェース:SerializableCharSequenceComparable
  • 内部構造:本質的に変更不可能(immutable)なデータ構造を持ちます。

インスタンス化と不変性(Immutability)

String オブジェクトを作成する方法はいくつかありますが、特に重要なのはその「不変性」です。一度生成された String の内容は後から変更することができず、値を変更しようとすると新しいオブジェクトが生成されます。

package com.example.stringdemo;

public class StringConceptDemo {

    // カスタム参照型を持つヘルパークラス
    static class DataHolder {
        public String message;
        public DataHolder(String msg) {
            this.message = msg;
        }
    }

    public static void main(String[] args) {
        // 1. リテラルによる定義(文字列プールを使用)
        String literalStr = "initial";
        String refToLiteral = literalStr;
        literalStr = "updated"; // 新しい String オブジェクトへの参照付け替え
        
        // refToLiteral は元のオブジェクトを指したままなので変化しない
        System.out.println("リテラル再割り当て後: " + refToLiteral); // initial
        
        // 2. コンストラクタによる明示的な生成(ヒープに新規作成)
        String constructedStr = new String("data");
        System.out.println(constructedStr.equals("data")); // true (内容比較)
        
        // 3. 参照型の挙動との対比
        DataHolder holder = new DataHolder("contentA");
        DataHolder alias = holder;
        holder.message = "contentB"; // 内部フィールドの変更
        
        // alias も同じオブジェクトを指すため、影響を受ける
        System.out.println("参照型の共有結果: " + alias.message); // contentB
    }
}

上記のように、標準的な参照型(DataHolder など)ではメモリアドレスは同一のまま内容が変わりますが、String では再代入時に内部配列(過去は char[]、新版では byte[])自体が final で固定されているため、実質的に値そのものが変わるとみなされ、新しいメモリ領域が確保されます。

代表的なメソッドの使用例

String クラスには多数のユーティリティメソッドが用意されています。主に比較、取得、変換、分割に分けて確認します。

比較およびハッシュ計算

// == 演算子 vs equals メソッド
String strX = new String("test");
String strY = new String("test");
System.out.println(strX == strY);        // false (アドレス比較)
System.out.println(strX.equals(strY));   // true (内容比較)

// ハッシュコードの仕組み
// 公式: h = 31 * previous_h + character_code
System.out.println("abc".hashCode()); 
// 'a'(97), 'b'(98), 'c'(99) の順で計算されます

compareTo メソッドを使うと辞書順での比較が可能です。辞書順が正しい場合 0 を返し、異なる場合は差の符号付き整数を返します。

// 辞書順比較
int result = "apple".compareTo("apply"); 
// 'e' (101) - 'y' (121) = -20
System.out.println(result); 

// 長さが異なる場合
System.out.println("longer".compareTo("short")); 
// 先頭が一致せず、長さで決まる場合は末尾までの桁数差が返されます

文字列操作と抽出

文字を取得したり、部分文字列を切り出したりする処理は頻繁に行われます。

String text = "JavaProgramming";

// 単一文字の取得(インデックスは 0 から始まります)
char firstChar = text.charAt(0); // 'J'

// サブストリングの抽出(左端インクルード、右端エキスクルート)
String sub = text.substring(0, 4); // "Java"

// 連結(頻繁に行う場合は StringBuilder を推奨)
String joined = "Hello".concat(", World");

// キーワード検索・置換
boolean hasJava = text.contains("Java");
String replaced = text.replace("Programming", "Development");

// 大文字小文字の統一(等価チェック用)
if ("TEST".equalsIgnoreCase("test")) {
    // true
}
text.toLowerCase(); // すべて小文字に変換した新オブジェクト

正規表現による分割

split メソッドを使用して文字列を配列に変換できます。デリミタとして正規表現を利用するため、特殊文字には注意が必要です。

String raw = "user:id:12345";
String[] parts = raw.split(":");
// ["user", "id", "12345"]

// 特殊文字バックスラッシュ (\) の場合
String path = "C:\\temp\\file.txt";
// 正規表現エスケープのため \\\\\\\\ が必要になります
String[] segments = path.split("\\\\\\\\"); 

// ピリオド (.) や_pipe|_も同様にエスケープが必要です
String data = "val1|val2|val3";
String[] tokens = data.split("\\|"); 

可変な文字列クラスとの使い分け

ループ内での繰り返し結合など、頻繁な変更が発生するシナリオでは String の利用は非効率です(大量の一時オブジェクトが発生するため)。その代わりに、以下の変更可変クラスを使用します。

  • StringBuilder: スレッドセーフではないため、シングルスレッド環境で最も高速です。
  • StringBuffer: メソッドが synchronized されており、マルチスレッド環境での使用に適しています。
StringBuilder builder = new StringBuilder("Start");
builder.append("-Middle-");
builder.append("-End");
// メモリアドレスを維持したまま内容拡張が可能

タグ: Java string-api immutability hashCode regex

7月31日 02:07 投稿