コード public class Sample{ public static void main(String[] args){ int k = 10; int x[] = {1, 2, 3, 4, 5}; int x2[] = {6, 7, 8, 9}; display(k, x, x2); change(k, x, x2); display(k, x, x2); } public static void change(int k, int x[], int x2[]){ k = 7; int y[] = {11, 12, 13, 14, 15}; x = y; x2[0] = 100; } public static void display(int k, int x[], int x2[]){ for(int i = 0; i < x.length; i++){ System.out.print(x[i] + " "); } System.out.println(""); System.out.println(k); for(int i = 0; i < x2.length; i++){ System.out.print(x2[i] + " "); } System.out.println(""); } } 実行結果 1 2 3 4 5 10 6 7 8 9 1 2 3 4 5 10 100 7 8 9
上のプログラムはk,x,x2を表示して、値を変更してから再度表示するプログラムです。
changeメソッドでk,x,x2ともに変更したのにx2しか変わっていませんでした。
引数として引き渡す場合
基本データ型はメソッド外部の内容がわからない。
配列やオブジェクトは参照がコピーされるだけなのでメソッド外部の内容がわかる。
ようなことを聞いたのですが、x,x2は配列なのにxは変わってないのにx2は変わりました。
結局何が理由で変更されるのとされないのがあるのか教えっていただきたいです。
よろしくお願いします。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答6件
0
changeメソッド内でxが参照している2つの配列を実体A、実体Bとして説明します。
実体A:{1,2,3,4,5}
実体B:{11,12,13,14,15}
changeメソッドに引数が渡され、処理が開始する際の「mainメソッド内のx」と「changeメソッド内のx」が指しているのは以下の通りです。
main.x:実体Aを指す change.x:実体Aを指す
ここで大事なことは、「main.x」と「change.x」は「同じ実体を指しているだけの別の変数」であるということです。
changeメソッド内で実体Bが定義され、x = y
でchange.xの参照先が実体Bに変わります。
つまり、
main.x:実体Aを指す change.x:実体Bを指す
という状態になります。
変わっているのはchange.xの参照先だけなので、main.xは変わらず実体Aを指し続け、値も変わりません。
また、xが変わらないのにx2が変わるのは、
x = y :change.xの参照先を、y(が指している実体B)に向ける。
x2[0] = 100 :change.x2の参照先はそのままで、参照先の中身(0番目の要素)を変更している。
という違いによるものです。
投稿2016/09/21 01:25
編集2016/09/21 01:27総合スコア408
0
ベストアンサー
change(int k, int[] x, int[] x2)
のx
というのは、change()
メソッド内のみで利用できる変数で、main()
メソッドで宣言されたx
の参照を上書きしているわけではありません。
以下の図で説明します。
便宜的に、hoge()
メソッド内の変数v
をhoge.v
と表記します。
例えば、質問中のmain()
メソッド内のint[]型変数x
をmain.x
とします。
(もちろんコード中で書くとエラーになりますが…)
また、{1, 2, 3, 4}
は配列、->
と<-
は変数が配列を指していることを表します。
change()
メソッドが呼び出された時点では以下のようになっています。
main.x -> {1, 2, 3, 4, 5} <- change.x main.x2 -> {6, 7, 8, 9} <- change.x2
つづいて、y
が宣言されます。
main.x -> {1, 2, 3, 4, 5} <- change.x main.x2 -> {6, 7, 8, 9} <- change.x2 {11, 12, 13, 14, 15} <- change.y
その次の行(x = y
)で、change.x
は「change.y
が指しているもの」を指すように指定します。
main.x -> {1, 2, 3, 4, 5} main.x2 -> {6, 7, 8, 9} <- change.x2 change.x -> {11, 12, 13, 14, 15} <- change.y
このとき「change.x
が指している配列」の情報を更新しても、「main.x
が指している配列」の情報は更新されないのです。
さて、最後に、change.x2
の0番目の要素を変更します。
main.x -> {1, 2, 3, 4, 5} main.x2 -> {100, 7, 8, 9} <- change.x2 change.x -> {11, 12, 13, 14, 15} <- change.y
このとき、「change.x2
の指している配列」と「main.x2
の指している配列」は同一であるため、「main.x2
の指している配列」も更新されています。
これが、main()
から見えるx
が更新されていない理由と、x2
が更新されている理由です。
投稿2016/09/20 08:11
総合スコア740
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2016/09/21 01:22 編集
2016/09/26 23:50
0
Java では配列もオブジェクトです。
オブジェクトの変数は、オブジェクトの参照を保持しています。
public static void change(int k, int x[], int x2[]) { k = 7; int y[] = {...}; x = y; // change 引数のx は y を指すが、呼び元(main) のxは変わらない x2[0] = 100; // change 引数 x2 == 呼び元の x2 なので、呼び元のx2[0] が変わる
投稿2016/09/20 08:08
総合スコア100
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2016/09/21 00:52
0
大雑把な基本(プリミティブ型の場合)
仮引数(呼び出された側で受け取ったもの)は実引数(呼び出した側で引き渡したもの)のコピーでしかない。
上記からいえること
仮引数に変更を加えても(それだけでは)実引数に影響はない。
プリミティブ型でない場合(オブジェクトの場合)
オブジェクト(配列含む)を格納した変数は「参照情報」を保持している。「参照情報」も引数渡しの際にコピーされることは上記と同じではある。ただし、「参照の先を変更」した場合は呼び出し型にも影響を与える。もっとも、実引数そのものが変更されるわけではなく、この参照先が変更されるわけである。
ちょっと書き方変えてみました。
投稿2016/09/20 12:00
総合スコア4830
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
他の回答者と同じことを、別の言い方で言います。
参照型について念のため書きます。
◆参照型
参照型の本体はメモリーヒープ上に存在する。参照型変数は(暗黙に)メモリーヒープを指している。
参照型変数と、ヒープメモリに存在する実体(配列の値そのもの)は別の領域である。
結局何が理由で変更されるのとされないのがあるのか教えっていただきたいです。
実引数、k, x x2はどれも変わっていません。仮引数にコピー(代入)しただけですから変わりません。
x2[0]が100に変わったのは、参照先のヒープ上の配列の要素[0]が100に置き換えられたから。
◆Javaのメソッド呼び出しは値呼び
メソッドの仮引数は実引数のコピー(値呼び)である。コピーとは変数の代入である。
メソッドの仮引数の有効範囲はメソッドのブロック内である。ブロック退出とともに仮引数は捨てられる。
これを踏まえて、メソッド呼び出しを、ネストしたブロックの処理に置き換えます。
以下のコードは、メソッドを呼ぶのと同じ処理になります。行コメントを読んでください。
Java
1int k = 10; // プリミティブ型の変数はスレッドスタックに割り当てられる。 2int x[] = {1, 2, 3, 4, 5}; // 配列はヒープ上のアドレスm番地に割り当てられたものとする。 3int x2[] = {6, 7, 8, 9}; // 配列はヒープ上のアドレスn番地に割り当てられたものとする。 4 5 6// メソッド呼び出しのつもり 7{ 8 // 以下の変数が引数のつもり 9 int kLocal = k; // kの値10がklocalにコピーされる。 10 int xLoacl[] = x; // xの参照のコピーが渡される。m番地を指している。 11 int x2Local[] = x2; // x2の参照のコピーが渡される。n番地を指している。 12 13 14 kLocal = 7; 15 int y[] = {11, 12, 13, 14, 15}; // 配列はヒープ上のアドレスl番地に割り当てられたものとする。 16 xLoacl = y; // xlocalの参照先、n番地がl番地に変更された。 17 18 x2Local[0] = 100; // n番地のオフセット0:配列要素[0]の値を100に置き換える。 19 // この変更だけが、ヒープの値に影響を与える。 20 21 22 // ブロック退出に伴い、ローカル変数、kLocal, xLoacl[], x2Local[], y[]は消滅する。 23 // y[]とxLoacl[]の参照先の配列{11, 12, 13, 14, 15}はゴミになる。 24} 25// k, x[], x2[]の値はもとのままで変わっていない。k : 10, x[] m番地, x2[] n番地 26// 変わったのは参照先のヒープの内容。n番地の配列要素[0] 27
###参照について(追記)
Q. 参照型変数とプリミティブ型変数の違いはなんですか?
A. 変数は次の種類があります。
・プリミティヴ型変数(値そのものが格納される変数)
・参照型変数(配列とオブジェクトを参照する。参照型変数の中身はint型の値)
・型変数(ジェネリクスで使用する。議論の対象外なので説明を省略します)
参照型変数に格納する整数値は、ヒープ領域に格納されたオブジェクトの本体を指すために使う。
ヒープ領域は大きなメモリ領域で、その中に自由にオブジェクトを割り当てて使える。
Q. 参照型変数の中身を見ることが来ますか?
A. System.identityHashCode(); を使うと見ることができます。2つの参照型変数の値が同じなら、同じオブジェクトを指します。
参照がnullの場合
Java
1int[] a = null; 2int hash = System.identityHashCode(a); 3System.out.println(hash);
a はnull。参照がないので 0 が表示されます。
参照に配列を割り当てる
Java
1a = new int[] {1,2,3,4,5,6}; 2hash = System.identityHashCode(a); 3System.out.println(hash);
配列をnew すると、ヒープに int型 6 個分の領域を割り当てる。
割り当てたint型 6 個を、それぞれ、1, 2, 3, 4, 5, 6 に初期化する。
ヒープの配列への参照値(数値)を参照型変数 a に代入する。
a (nnnnnnn) ———> 配列の本体 | 1 2 3 4 5 6 |
別の参照に参照を代入する
Java
1int[] b = null; 2b = a; 3hash = System.identityHashCode(b); 4System.out.println(hash);
b には a の参照値(nnnnnnn)が代入される。
代入によって b が a そのものを指すわけではない。(重要)
a と b は何の関係もなく、それぞれがヒープの同じ配列を指しているだけ。
参照を比較する
Java
1boolean isSame = a == b; 2System.out.println(isSame); 3 4isSame = System.identityHashCode(a) == System.identityHashCode(b); 5System.out.println(isSame);
a == b は実は、参照値(nnnnnnn)を整数比較しているだけ。
###メソッドの引数の有効範囲(追記)
プリミティヴ型 k が引数の場合
Java
1static void change(int k) { 2 k = 7; 3}
メソッドが呼ばれると、ローカル変数 k がとられる。(k は実引数とはなんの関係もない)
k に実引数の値 10 がコピーされる。
k に 7 を代入する。
メソッド退出時に k を破棄する。このメソッドは何も有意義なことはしない。
参照型 x2[] が引数の場合
Java
1static void change1(int[] x2) { 2 x2[0] = 100; 3}
メソッドが呼ばれると、ローカル変数 x2 がとられる。(x2 は実引数とはなんの関係もない)
x2 に実引数の参照値(nnnnnn)がコピーされる。
参照値が参照する配列の本体の要素0の値は 6。要素0の値を 100 に置き換える。
メソッド退出時に x2 を破棄する。
x2 は破棄されるが、ヒープの配列の本体は変更されたまま。このメソッドは有意義なことをしている。
参照型 x[] が引数の場合
Java
1static void change1(int[] x) { 2 x = new int[] {11,12,13,14,15}; 3}
メソッドが呼ばれると、ローカル変数 x がとられる。(x は実引数とはなんの関係もない)
x に実引数の参照値(nnnnnn)がコピーされる。
配列をnew する。ヒープに int型 5 個分の領域を割り当てる。
割り当てたint型 5 個を、それぞれ、11, 12, 13, 14, 15 に初期化する。
ヒープの配列への参照値(mmmmmm)を参照型変数 x に代入する。
今や x の参照値は、別の配列を指すようになった。
メソッド退出時に x を破棄する。
参照値(mmmmmm)が指す配列の本体を誰も参照しないので、GCの対象になる。
このメソッドは何も有意義なことはしない。
参照型 x[] が引数で勝つ返り値に参照を返す場合
Java
1static int[] change3(int[] x) { 2 return new int[] {11,12,13,14,15}; 3}
メソッドが呼ばれると、ローカル変数 x がとられる。(x は実引数とはなんの関係もない)
x に実引数の参照値(nnnnnn)がコピーされる。
しかし x を使わずに、新たな配列を生成して、その参照をメソッドの返り値として返す。
配列をnew する。ヒープに int型 5 個分の領域を割り当てる。
割り当てたint型 5 個を、それぞれ、11, 12, 13, 14, 15 に初期化する。
ヒープの配列への参照値(mmmmmm)を返り値として返す。
メソッド退出時に x を破棄する。
メソッドの返り値を実引数 x に代入すれば、x の配列が丸ごと入れ替わる。
このメソッドは値を返すことで有意義なことをしている。
説明は以上です。質問は受け付けます。気軽にどうぞ。
投稿2016/09/20 11:26
編集2016/09/21 11:50退会済みユーザー
総合スコア0
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
言語仕様4.12.5. Initial Values of Variables
Each method parameter (§8.4.1) is initialized to the corresponding argument value provided by the invoker of the method (§15.12).
実引数で仮引数が初期化されます。
changeメソッドでk,x,x2ともに変更した
chageメソッドではx2が参照する要素の1番目の値を変更しています。よって、呼び出し側で宣言している配列x2の要素の内容が変わります。
結局何が理由で変更されるのとされないのがあるのか
配列の要素に代入、オブジェクトに対してフィールドを変えるメソッドを呼び出す場合が代表的でしょうか。以下の2つのメソッドが良い例だと思います。
・Arrays#fill()メソッド
・Collections#addAll()メソッド
投稿2016/09/20 08:06
編集2016/09/20 08:07総合スコア452
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2016/09/27 00:05