スポンサーリンク

Javaの動かないコード(初級編) メソッド引数の 「参照のコピー渡し」を理解していない

下記のJavaコードが,意図した通りに動作しないのはなぜですか。(制限時間:1分)

  • オブジェクトのプロパティを更新して,それぞれ3, 4と表示したい。


Hoge.java

public class Hoge {
	
	// 数値を保持するクラス。
	static class Fuga {
		public int x;
	}

	public static void main( String[] args )
	{
		// オブジェクトを新規作成。
		Fuga f1 = new Fuga();
		f1.x = 1;
		
		// プロパティの値を更新。
		func1( f1 );
		
		// プロパティの値を表示。
		System.out.println( f1.x );



		// オブジェクトを新規作成。
		Fuga f2 = new Fuga();
		f2.x = 2;
		
		// プロパティの値を更新。
		func2( f2 );
		
		// プロパティの値を表示。
		System.out.println( f2.x );
	}
	
	private static void func1( Fuga f )
	{
		// 参照渡しで渡されたオブジェクトのプロパティの値を新しくする。
		f.x = 3;
	}
	
	private static void func2( Fuga f )
	{
		// 参照渡しで渡されたオブジェクトを新規オブジェクトで入れ替える。
		f = new Fuga();
		
		// プロパティの値を新しくする。
		f.x = 4;
	}

}

実行結果


3のほうはOK。

4とは表示されない。2 と表示される。


原因

Javaでは,メソッド引数において,オブジェクトは

  • 値渡しではない。
  • 参照渡しではない。
  • 参照のコピー渡しである。

したがって,メソッド内から,メソッドの呼び出し元に対して影響を及ぼせる場合と,影響を及ぼせない場合が生じてくる。



具体的に,冒頭のコードを例にとって解説する。


main内で作成されたFugaのインスタンスは,そのものが直接メソッドに渡されるのではない。(値渡しではない。)

インスタンスへの「参照のコピー」がメソッドに渡る。

つまり,インスタンスが格納されたメモリアドレスがコピーされて渡される。(単純な参照渡しではない。)



func1の場合,メソッド呼び出しもとのオブジェクトの格納アドレスがコピーとして渡ってきて,そのまま使っている。

なので,呼び出しもとのオブジェクトをそのまま操作することになる。

これは問題ない。



func2の場合,新規オブジェクトを生成した時点で,その新規オブジェクトを格納するために,新しくメモリのアドレスが割り当てられる。

そして,その「新しく割り当てられたアドレス」が,メソッド内で変数に代入されている。


なのでメソッド内では,呼び出しもとのオブジェクトではなく,メソッド内で新しく生成されたオブジェクトを操作していることになる。

メソッド内の変数は,呼び出しもとのオブジェクトへの「参照のコピー」に過ぎない。

なので,呼び出しもとのオブジェクトには影響を及ぼすことができない。


参照の「コピー」という点がキモ。


結論

結論として,メソッド引数にオブジェクトを渡すと

  • 基本的には,メソッド内の操作が,呼び出し時引数に影響を与える。
  • メソッド内で新規オブジェクトに置き換えようとした場合は,呼び出し時引数に影響しなくなる。

Javaの基礎だが,たまに忘れるとはまる。


特に,メソッド内で文字列を代入してはまるケース:

わかりずらい Java の参照渡し (1/3)
http://www.tec-q.com/note/2007/04/_ja...

  • 文字列 " " は,それ自体が新規インスタンス生成である,という点を忘れずに。


Javaの参照渡しについて
http://detail.chiebukuro.yahoo.co.jp/...

  • メソッド内で引数への文字列の新規代入は,既存のポインタの破棄である


Javaの仮引数と実引数
http://isol.pro-s.co.jp/news/2008/12/...

  • 新しいインスタンスを生成してから,そのインスタンスについてアドレスの値を上書いても,もとのインスタンスには影響しない