基本データ型と参照型の違い
初心者にとって、ちょっと難しい「参照型」の変数。基本データ型と何が違うのでしょうか。配列への代入やStringの例を使って、変数の中に何が入っているのかを考えてみましょう。
参照型の反対は?
Javaの変数の型は、大きく分けると基本データ型(プリミティブ型)と参照型の2種類です。intやdoubleなどは基本データ型、配列やStringなどは参照型です。
基本データ型の変数には、数値などの値そのものが入ります。一方、参照型の変数には、配列やオブジェクトを指す参照値が入ります。
参照値と言われても、まだよく分からなくて大丈夫です。まずは、基本データ型の変数から見ていきましょう。
基本データ型変数のメモリ使用領域
基本データ型には8種類あり、数値型では値を何bitで表すかが決まっています。bit(ビット)は、0か1を表す単位です。
| 型 | 値を表すビット数 |
|---|---|
| boolean | 記憶領域の大きさは規定なし |
| byte | 8bit |
| char | 16bit |
| short | 16bit |
| int | 32bit |
| float | 32bit |
| long | 64bit |
| double | 64bit |
それぞれの型で扱える値については、基本データ型の記事で説明しています。
例えば、int型の変数aを宣言してみます。以下の部分的なコード例は、Test.javaのmainメソッド内に書くものとして読んでください。各例は別々の例です。
Test.java
int a;変数を箱に例えるなら、これは32bitの整数を入れる箱を用意するイメージです。そこに5を代入します。
Test.java
int a;
a = 5; 5を2進数で表すと101ですが、intの値は32bitで表します。先頭に0を補うと、00000000000000000000000000000101です。
5だから3bit、大きな数ならもっと多くのbit、というように値によってintのビット数が変わるわけではありません。32bitで表せる整数の範囲は、−231〜231−1です。
参照型変数とは?
では、配列の場合はどうでしょうか。
Test.java
int[] a; // ①
a = new int[]{1, 2, 3}; // ②①では配列を指すための変数aを宣言しています。この時点では、配列そのものを作っているわけではありません。②で3要素の配列を作り、その配列を指す参照値をaに代入しています。
配列には3要素のものも、10要素のものもあります。配列全体を変数aの箱の中に入れるのではなく、配列の本体は別にあり、aにはその配列を指す参照値が入ると考えてください。
参照値は、目的の配列やオブジェクトにたどり着くための手掛かりのようなものです。aを使って配列を操作すると、aが指している配列にアクセスできます。
これが「参照型」と呼ばれる理由です。参照型の変数には、何も指していないことを表すnullを入れることもできます。
値の代入と参照値の代入
それでは、問題です。次のプログラムを実行すると、問1と問2ではそれぞれ何が表示されるでしょうか。
Test.java
public class Test {
public static void main(String[] args) {
/* 問1 */
int[] a = new int[]{1, 2, 3}; // ①
int[] b = a; // ②
b[0] = 5; // ③
System.out.println(a[0]);
/* 問2 */
int c = 1; // ④
int d = c; // ⑤
d = 5; // ⑥
System.out.println(c);
}
}問1では、aをbに代入した後、bの0番目の要素を書き換えています。問2では、cをdに代入した後、dを書き換えています。aとcにも変化があるのか、考えてみてください。
5
1 問1の①で、aには配列を指す参照値が入ります。②のint[] b = a;では、この参照値がbにコピーされます。配列そのものがコピーされるわけではないので、aとbは同じ一つの配列を指しています。
そのため、③のb[0] = 5;で書き換えた要素は、aから見ても同じ要素です。a[0]を表示すると5になります。
一方、問2の④でcに入るのは整数の1です。⑤のint d = c;では、整数の1がdにコピーされます。⑥でdを5にしても、cの値は変わらないので、表示されるのは1です。
| 代入 | コピーするもの | 代入した後 |
|---|---|---|
int[] b = a; | 配列を指す参照値 | aとbは同じ配列を指す |
int d = c; | 整数の値 | cとdはそれぞれ整数を持つ |
どちらも「変数に入っている値をコピーする」という点は同じです。コピーされるのが整数なのか、配列を指す参照値なのか、という違いですね。
オブジェクトと参照型
ここまでは配列を例に説明しましたが、クラスやインターフェースの型も参照型です。クラスのインスタンスも配列も、オブジェクトとして扱われます。
よく使うStringもクラスなので、String型の変数は参照型です。
Test.java
String s = "あいうえお";sには「あいうえお」という文字列そのものではなく、そのStringオブジェクトを指す参照値が入っています。Stringについて詳しくは、Stringの記事も参照してください。
ところで、Stringのオブジェクトは、一度作ると内容を書き換えることができません。「いやいや、書き換えられるでしょう?」と思った人もいるかもしれません。
Test.java
String s = "あいうえお";
s = "かきくけこ";確かに、これでsを表示すれば「かきくけこ」になります。しかし、「あいうえお」というオブジェクトの中身を書き換えたわけではありません。
2行目では、「かきくけこ」という別のStringオブジェクトを指すように、sの参照値を入れ替えています。変わったのは、オブジェクトの内容ではなく、変数sが指す先です。
なお、ダブルクォーテーションで書いた文字列は、既にあるオブジェクトが再利用されることもあります。代入するたびに必ず新しいStringオブジェクトが作られる、という意味ではありません。
equals( )と、比較演算子「==」
Stringのequalsメソッドは、文字列の内容が同じかどうかを調べるメソッドです。例えば、次のように使います。
Test.java
String s = "あいうえお";
String t = "あいうえお";
boolean b = s.equals(t);
System.out.println(b);true sとtが指す文字列の内容は同じなので、trueになります。では、比較演算子の==を使った場合はどうでしょうか。
Test.java
String s = "あいうえお";
String t = "あいうえお";
boolean b = (s == t);
System.out.println(b);trueこの例もtrueになります。ただし、参照型の==は、内容ではなく、同じオブジェクトを指しているかどうかを比較しています。
このように同じ文字列リテラル(ダブルクォーテーションで書いた文字列)を使う場合、同じStringオブジェクトが共有されます。そのため、この例ではsとtの参照先が同じになり、==でもtrueになるのです。
違いを確かめるため、今度はnewを使って、同じ内容のStringオブジェクトを別々に作ってみます。
Test.java
String s = new String("あいうえお");
String t = new String("あいうえお");
System.out.println(s.equals(t));
System.out.println(s == t);true
false文字列の内容は同じなのでequalsはtrueですが、別々のオブジェクトなので==はfalseです。文字列の内容を比べたい時は、Stringのequalsを使いましょう。
最後に、次のようにsをそのままtに代入した場合はどうでしょうか。
Test.java
String s = "あいうえお";
String t = s;
boolean b = (s == t);
System.out.println(b);truesの参照値をtにコピーしているので、sとtは同じオブジェクトを指します。そのため、==の結果はtrueです。配列の例でaをbに代入した時と同じですね。
まとめ
質問・コメント
読み込み中この記事についての質問や、学習して気づいたことをお寄せください。
この記事のコメント
旧サイトに寄せられたコメントも当時の投稿内容のまま掲載しています。現在の記事では変更している内容や、旧環境についてのやり取りも含まれます。
コメントを読み込んでいます。