Java String クラスの不変性および連結処理の内部構造

String クラスのインスタンスは、生成された後にその内部状態を変更することができません。これは「不変性(Immutability)」と呼ばれる特性であり、一度生成された String オブジェクトの値を変更することは不可能です。

参照変数とオブジェクトの関係

以下のコード例を見ると、変数の値が変更されているように見えますが、実際にはオブジェクト自体が書き換えられているわけではありません。

String text = "Apple";
System.out.println("current = " + text);

text = "Orange";
System.out.println("current = " + text);

出力結果:

current = Apple
current = Orange

一見すると値が変わったように見えますが、これは変数 text が参照しているオブジェクトが切り替わっただけです。最初の "Apple" というオブジェクトはメモリ上に残ったままであり、変数 text が新たに生成された "Orange" という別のオブジェクトを指すように変更されています。したがって、変化しているのは参照先であり、オブジェクト自体の不変性は保たれています。

内部実装と final 修飾子

String クラスが不変である理由は、JDK のソースコードを確認すると理解できます。字符串のデータは内部で配列によって管理されており、その配列自体が final 修飾子で定義されています。

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];
    // ...
}

クラス自体が final であり、内部の配列フィールドも final であるため、外部から内部データを書き換えることはできません。String クラスが提供するメソッドは、内部配列を変更するのではなく、変更結果を反映した新しい String インスタンスを生成して返す仕組みになっています。

文字列の連結処理

文字列を結合する方法には、concat() メソッドを使用する方法と、+ 演算子を使用する方法があります。

String partA = "Hello";
String partB = "World";
String result1 = partA.concat(partB);       // メソッドによる連結
String result2 = "StaticA".concat("StaticB"); // 定数の連結
String result3 = partA + partB;             // 演算子による連結
String result4 = "StaticA" + "StaticB";     // 定数の演算子連結

+ 演算子を用いた連結は、内部的には StringBuilder オブジェクトを生成し、append メソッドで結合を行った後に toString() を呼び出して String オブジェクトを返す処理に変換されます。

ただし、複数の文字列リテラル(定数)を + で結合した場合、コンパイル時に最適化が行われます。この場合、StringBuilder は使用されず、複数の定数が事前に結合された単一の文字列定数として処理されます。文字列定数はメソードエリア(または字符串定数プール)に格納され、変数によるインスタンスはヒープ領域に格納されるという違いがあります。

字符串定数プールとメモリ比較

文字列リテラルと new キーワードによる生成、そして連結演算の最適化は、メモリ上のアドレス比較(==)に影響を与えます。

String str1 = "Technology";
String str2 = new String("Technology");
String str3 = "Tech";
String str4 = "nology";
String str5 = "Tech" + "nology";
String str6 = str3 + str4;

System.out.println(str1 == str2);   // false
System.out.println(str1 == str5);   // true
System.out.println(str1 == str6);   // false

この結果から、以下のことがわかります。

  • str1 == str2: リテラルと new インスタンスは異なるオブジェクトのため false。
  • str1 == str5: コンパイル時に "Tech" + "nology" が "Technology" に最適化され、プール内の同一オブジェクトを参照するため true。
  • str1 == str6: 変数同士の連結は実行時に処理され新しいオブジェクトが生成されるため false。

タグ: Java string-internals immutability string-pool jdk-source-code

8月8日 15:26 投稿