ポインタとは
ポインタとは、変数や関数などのオブジェクトが、メモリ上のどの位置(番地)存在するのかを指し示す変数です。
C言語(またはC++)では、特にポインタを意識したプログラミングが必要なことが有名で、例えば、引数にオブジェクトを渡す場合、オブジェクトをコピーして渡すというのが基本の動きになり、呼び指し先の関数で引数の値を書き換えても、呼び出し元の変数には影響を与えません。
もし、変数の参照を渡して呼び指し先で引数の値を書き換えたいときは、「&」(アンパサンド)を用いて、変数のメモリ上のアドレスを取得し、ポインタ変数として関数の引数に渡します。
このように C言語は、メモリのアドレスを直接使用して値を参照したり、書き換えたりできるため、よりハードウェアに近い位置で制御が行える反面、誤って不正なメモリのアドレスを参照して予期せぬデータを読み取ったり、アクセス違反によりプログラムが異常終了することがしばしばありました。
Java にポインタはある?
Java にはポインタ変数のような、メモリのアドレスを直接指定してデータにアクセスする機能はありません。これには、ポインタ変数は、それ自体が複雑で使用が難しく、ソースコードが複雑化してしまうことや、不正なメモリ領域にアクセスしないための安全性などの理由があります。
ただし、ポインタという概念が無くったわかではありません。
Java では、参照型の変数は暗黙的にオブジェクトへのポインタ変数として扱い、int 、long などの基本型(プリミティブ型)は、値そのものを変数にもつルールがあります。ルール化することにより、C 言語などのようなポインタ変数の複雑性をなくし、プログラムを読みやすくしており、Java 以外の C# などのプログラム言語でも同じようなアプローチがとられています。
値渡し
値渡しとは、変数の値を自体をメソッドの引数などに渡すことを言います。基本型(プリミティブ型)の変数を引数に渡すときは、すべてのこの値渡しになります。
実際に値渡しのサンプルコードを見てみましょう。
次のサンプルコードは、main メソッドの中で定義した 変数 value を、別の add メソッドに引数として渡して、add メソッドの中で引数の値に 10 を加算する例です。
public class Main {
// 最初に呼ばれるメインメソッド
public static void main(String[] args){
int value = 10;
add(value);
System.out.println("value=" + value);
}
// 引数の値に10を足すメソッド
public static void add(int value) {
value = value + 10;
}
}
上のコードを実行すると、次の結果になります。
value=10
変数 value の値を add メソッドの中で加算していましたが、main メソッドの中で定義した value の値には変更がありません。
これは、値渡しの場合、単純に変数の値のコピーを引数に渡しているため、呼び出し先のメソッドで変数の値をいくら変更しても、呼び出し元の変数には影響を与えないためです。
値渡しの対象となるデータ型
基本型(プリミティブ型)の変数はすべて値渡しの対象になります。
Java の基本型(プリミティブ型)は、次の8種類です。
- byte型
- short型
- int型
- long型
- float型
- double型
- char型
- boolean型
【関連記事】
▶【Java】データ型に関する基礎知識 基本型と参照型を把握しよう
参照渡し(ポインタ渡し)
冒頭でも述べたように、Java では ArrayList クラスなどをはじめとる、基本型(プリミティブ型)以外の参照型の変数を引数に渡すときは、参照渡しとなります。
これは、参照型の変数には、対象のオブジェクトが格納されているポインタ(メモリ上のアドレス)が格納されていることにより、呼び出し先のメソッドでも同じメモリ上のアドレスを参照する変数となり、結果的に、呼び出し元の変数が参照できているかのような動きになります。
実際に、参照型の変数を引数に渡す例を見てみましょう。
次のコードは、String の配列をメソッドの引数に渡し、呼び出し先のメソッドで配列の一部の要素の値を書き換える例です。配列の変数は参照型となるため、以下の例は参照渡し(ポインタ渡し)となります。
public class Main {
// 最初に呼ばれるメインメソッド
public static void main(String[] args){
String[] array = {"Java", "Python", "Ruby"};
change(array);
for (String str : array) {
System.out.println(str);
}
}
// 配列を引数で受け取り、2つ目の要素の値を書き換えるメソッド
public static void change(String[] array) {
array[1] = "C++";
}
}
実行結果は次のとおりです。配列の2つ目の要素が、change メソッドの中で書き換えられた値に変わっていることが分かります。
Java
C++
Ruby
基本型(プリミティブ型)でも参照渡しをしたい
基本型(プリミティブ型)でも参照渡しにすることで、別のメソッドの中で変数の値を書き換えたいと思う方もいるでしょうが、Java では 基本型(プリミティブ型)は値のコピーしか引数に渡すことができないため、参照渡しにしたい場合は、次のように配列型にしてポインタを渡すなどの対応が必要です。
public static void main(String[] args){
// 参照渡したい int の変数をいったん配列に入れる
int a = 10;
int[] ary = {a};
// 配列を引数で渡し、メソッドの中で値を書き換える
change(ary);
// 配列の中から変更された値を取り出す
a = ary[0];
System.out.println("a=" + a); // a=100
}
public static void change(int[] array) {
array[0] = 100;
}
Stringは値渡し?
Java では、文字列を格納する String も参照型です。つまり、String 型の変数をメソッドの引数にしていすると、その変数のポインタが渡る参照渡しとなります。
一部では、String 型だけは特殊な扱いで、参照型でも値渡しになると勘違いされる方もいますが、String 型も他の参照型と同様に引数にポインタが渡る参照渡しです。
ただし、String 型は、 immutable な(状態を変えられない)オブジェクトであるため、呼び出し先のメソッドで値を書き換えることが出来ません。(逆にこの性質が String 型は値私になるという誤解を生んでいるようです)
一部のサイトや書籍では、参照型の変数を引数で渡すときは、変数への参照が呼び出し先のメソッドに渡されなどと書かれている場合がありますが、厳密にはこれは誤りです。
Java では、メソッドの引数に変数を指定する場合、基本型(プリミティブ型)や参照型に関わらず、変数の中身をコピーした値を引数に渡します。これが、基本型(プリミティブ型)の時は、変数には値そのものが格納されているため値がコピーされ、参照型の時はポインタが格納されているという違いがあるだけです。