Javaの変数の型は、プリミティブ型と参照型に大別されます。
そして、変数の値が同じであるかどうかを確認する場合、プリミティブ型は == で同じ値であることを確認できる(同じ値の場合はTrueになる)のに対し、参照型の場合は原則として == では確認できず、equalsメソッドを使う必要があります。
しかし、参照型の場合であっても、== で確認できる場合があります。
それは、参照型の変数にリテラル(ソースコード内に直接記述された定数値)を直接代入した場合です。
以下で、順番を追って説明していきます。
【メモリーへの値の保持の方法】
変数の値はメモリーに保持されます。
メモリーは1バイト(8ビット)ずつ細かく区分けされており、それぞれの区分けについて「アドレス」と呼ばれる値により一意に場所を特定します。
(アドレスのバイト数は、64ビットOSの場合は8バイトです)
プリミティブ型も参照型も、「メモリーに保持される」ということは変わりませんが、メモリーに保持する値は変わります。プリミティブ型変数では値そのものをメモリーに格納するのに対し、参照型変数ではメモリー上にその変数用の保存領域を確保した上で、その保存領域の場所を指し示すアドレスを変数の領域に格納します。
例として、以下のソースコードについて考えます。
1 2 3 4 5 6 7 8 9 10 11 12 |
import java.util.*; public class Main { public static void main(String[] args) throws Exception { // プリミティブ型変数の例 int i1 = 1; // 参照型変数の例 int[] il1 = new int[] {1,2,3}; } } |
このソースコードの場合のメモリーのイメージは以下です。
プリミティブ型変数の「i1」と、参照型変数の「il1」では、値の保持の方法が異なります。
【リテラル用の領域の確保】
リテラルの場合は、リテラル用の領域が別途確保されます。
そして、同じ値のリテラル値が複数の箇所で記述されていたとしても、全ての箇所において同じ領域が使いまわされます。
参照型変数でインスタンス化した(newした)場合は、仮に同じ値だとしても、インスタンス化する度に異なる領域が確保されるため、その挙動とは異なるものとなります。
例として、以下のソースコードについて考えます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import java.util.*; public class Main { public static void main(String[] args) throws Exception { // リテラルのアドレスを代入 String s1 = "hoge"; String s2 = "hoge"; // 新たに領域を確保し、そのアドレスを代入 String s3 = new String("hoge"); String s4 = new String("hoge"); // 他のリテラルのアドレスを代入 String s2 = "fuga"; } } |
このソースコードの場合のメモリーのイメージは以下です。
リテラル値を代入した「s1」「s2」と、インスタンス化で新たに領域を確保した「s3」「s4」では、値の保持の方法が異なります。
【比較を行った場合の挙動の違い】
上記より、リテラル値を代入した場合と、インスタンス化を行った場合では、値の保持の方法が異なります。
この違いが表れるケースの一つとして、値が同一であるかどうかを確認するケースがあります。
リテラル値を代入した参照型変数同士を比較する場合は、インスタンス変数同士の比較と同じように、== で値が同一であることを確認できます。
(もちろん、equalsメソッドでも比較可能です)
しかし、インスタンス化を行った参照型変数の比較では、== で値が同一であることを確認することはできず、equalsメソッドを使用する必要があります。
また、equalsメソッドであれば、リテラル値を代入した参照型変数とインスタンス化を行った参照型変数の比較も可能です。
これをソースコードで示すと以下の通りです。
(実行結果となる標準出力はコメントで示しています)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import java.util.*; public class Main { public static void main(String[] args) throws Exception { // リテラルを代入する場合 String s1 = "hoge"; String s2 = "hoge"; System.out.println(s1 == s2); // true System.out.println(s1.equals(s2)); // true // インスタンス化する場合 String s3 = new String("hoge"); String s4 = new String("hoge"); System.out.println(s3 == s4); // false System.out.println(s3.equals(s4)); // true // リテラル値を代入した参照型変数とインスタンス化を行った参照型変数の比較 System.out.println(s1 == s3); // false System.out.println(s1.equals(s3)); // true } } |
リテラル用のメモリー領域の確保について深堀りする機会があったので、記事を書きました。
実務では、普段からString型のような参照型の変数をequalsメソッドで比較する癖をつけておけば大丈夫です。
しかし、リテラル代入と == の組み合わせで比較している場合、後の修正でリテラル代入の代わりにインスタンス化を使う箇所が現れると上手く比較できなくなるので、注意が必要です。
コメント