Do you really understand the immutability of Java strings?
1. 背景
文字列の不変性は、面接でよくある「シンプルな」問題と言えます。
一般的には次のように答えられます。
文字列は作成後に変更できません。
文字列の不変性とは、文字列の文字が不変であることを意味します。
String の value 文字配列は final として宣言されており、不変性を保証します。
しかし、それは本当に正しいのでしょうか?
さらに 2 つの問いを考えてみましょう。
文字列の不変性とは、具体的に何を意味するのでしょうか?
それはどのように保証されているのでしょうか?
ここで不思議な現象を見てみましょう。プログラムの最後で次の文が「aw」を出力しました。なぜでしょうか?
//前述のコードは省略
System.out.println("ab");
まず自分で考えてから、以下の説明を読むことをお勧めします。
2. 具体例
次のサンプルコードの出力は何になると思いますか?
予想と違いましたか?
リフレクションを使うと、文字列の文字配列を変更でき、その結果として文字列の「中身」が変化します。
ネイティブメソッドは、オブジェクトに固有のハッシュ値を付与します(hashCode メソッドがオーバーライドされているかどうかに関係なく)。
オブジェクト自体は変化していません。
では、String のハッシュ値は文字配列から計算されると分かっています(JDK8)。なぜ hashCode 関数の戻り値が 2 回とも同じなのでしょうか?
java.lang.String#hashCode のソースコードを詳しく見てみましょう。
1 回目の hashCode 関数の呼び出し後、ハッシュ値の計算結果は hash 属性を通じて文字列オブジェクトにキャッシュされます(キャッシュされている限り再計算されません)。そのため、2 回目は 1 回目と同じ値になります。
では、不変性はどのように保証されているのでしょうか?
まず、String を final として宣言し、継承できないようにしています。
次に、すべての変更メソッドは新しい文字列オブジェクトを返し、変更時に元のオブジェクトへの参照が変わらないようにしています。
文字列プールに存在する場合はその文字列オブジェクトを直接参照し、存在しない場合は新しい文字列オブジェクトを作成してプールに格納し、そのオブジェクトを参照します。
つまり、文字列の不変性とは参照の不変性を指していることが分かります。
String 内の value 文字配列は final として宣言されていますが、この final は value の参照を不変にするだけであり、文字配列の文字が置換できないようにするものではありません。
冒頭の「ab」と末尾の「ab」はどちらもリテラルで、同じ文字列プール内の同じオブジェクトを参照しているため、オブジェクトのプロパティ変更は両方の出力に影響します。
3. 考察
シンプルな質問は、見た目ほど単純ではないことが多いです。
技術ブログのソースコードを読むときは、必ず自分の考えを持ち、何度も「なぜ」と問いかけ、実践する機会を持ちましょう。
技術を学ぶときは、問題の本質について考えるという本質的な思考を養うべきです。
面接では、シンプルな質問に対しては包括的かつ深く答え、答えられない質問に対しては自分なりの考えを持つことで、より多くの機会が生まれます。
文字列の不変性は、面接でよくある「シンプルな」問題と言えます。
一般的には次のように答えられます。
文字列は作成後に変更できません。
文字列の不変性とは、文字列の文字が不変であることを意味します。
String の value 文字配列は final として宣言されており、不変性を保証します。
しかし、それは本当に正しいのでしょうか?
さらに 2 つの問いを考えてみましょう。
文字列の不変性とは、具体的に何を意味するのでしょうか?
それはどのように保証されているのでしょうか?
ここで不思議な現象を見てみましょう。プログラムの最後で次の文が「aw」を出力しました。なぜでしょうか?
//前述のコードは省略
System.out.println("ab");
まず自分で考えてから、以下の説明を読むことをお勧めします。
2. 具体例
次のサンプルコードの出力は何になると思いますか?
予想と違いましたか?
リフレクションを使うと、文字列の文字配列を変更でき、その結果として文字列の「中身」が変化します。
ネイティブメソッドは、オブジェクトに固有のハッシュ値を付与します(hashCode メソッドがオーバーライドされているかどうかに関係なく)。
オブジェクト自体は変化していません。
では、String のハッシュ値は文字配列から計算されると分かっています(JDK8)。なぜ hashCode 関数の戻り値が 2 回とも同じなのでしょうか?
java.lang.String#hashCode のソースコードを詳しく見てみましょう。
1 回目の hashCode 関数の呼び出し後、ハッシュ値の計算結果は hash 属性を通じて文字列オブジェクトにキャッシュされます(キャッシュされている限り再計算されません)。そのため、2 回目は 1 回目と同じ値になります。
では、不変性はどのように保証されているのでしょうか?
まず、String を final として宣言し、継承できないようにしています。
次に、すべての変更メソッドは新しい文字列オブジェクトを返し、変更時に元のオブジェクトへの参照が変わらないようにしています。
文字列プールに存在する場合はその文字列オブジェクトを直接参照し、存在しない場合は新しい文字列オブジェクトを作成してプールに格納し、そのオブジェクトを参照します。
つまり、文字列の不変性とは参照の不変性を指していることが分かります。
String 内の value 文字配列は final として宣言されていますが、この final は value の参照を不変にするだけであり、文字配列の文字が置換できないようにするものではありません。
冒頭の「ab」と末尾の「ab」はどちらもリテラルで、同じ文字列プール内の同じオブジェクトを参照しているため、オブジェクトのプロパティ変更は両方の出力に影響します。
3. 考察
シンプルな質問は、見た目ほど単純ではないことが多いです。
技術ブログのソースコードを読むときは、必ず自分の考えを持ち、何度も「なぜ」と問いかけ、実践する機会を持ちましょう。
技術を学ぶときは、問題の本質について考えるという本質的な思考を養うべきです。
面接では、シンプルな質問に対しては包括的かつ深く答え、答えられない質問に対しては自分なりの考えを持つことで、より多くの機会が生まれます。
Related Articles
-
A detailed explanation of Hadoop core architecture HDFS
Knowledge Base Team
-
What Does IOT Mean
Knowledge Base Team
-
6 Optional Technologies for Data Storage
Knowledge Base Team
-
What Is Blockchain Technology
Knowledge Base Team
Explore More Special Offers
-
Short Message Service(SMS) & Mail Service
50,000 email package starts as low as USD 1.99, 120 short messages start at only USD 1.00
