疲れたらやすむ

Javaを学ぶ上でハマったところを書いていきます。iPhoneアプリ開発や日常ネタもあるかも。

【Java】参照渡しと値渡し

今回は参照渡しと値渡しについてです。

一応最初に銘打っておきますが、ここで言う「参照渡し」と「値渡し」は以下の定義とさせていただきます。
参照渡し:呼び出したメソッド内で値を変更した場合、呼び出し元にも影響がある
値渡し:呼び出したメソッド内で値を変更しても、呼び出し元には影響がない

まずはそもそも参照渡しや値渡しの「渡し」ってなんぞや?というところからいきます。
渡しとはメソッドに引数を渡すことです。
例えば以下のメソッドを呼び出す場合にint型の変数を1つ渡す必要があります。

public void method(int value) {
	// 処理
}

その際の引数の渡し方に参照渡しと値渡しがあります。

それでは具体的に参照渡しと値渡しとはなんぞやと。
実際にプログラムを動かして確認してみます。

まず参照渡し。

ソース

public class Main {
	public static void main(String[] args) {
		Apple apple = new Apple(5);

		change(apple);
		System.out.println(apple.quantity);
	}

	public static void change(Apple apple) {
		apple.quantity = 10;
	}
}

class Apple {
	String name;
	int quantity;

	public Apple(int quantity) {
		this.name = "apple";
		this.quantity = quantity;
	}
}

実行結果

10

独自クラスであるApple型の変数を定義し、nameをapple、quantityを5で初期化しています。
その後、changeメソッドで引数のappleのquantityを10に変更します。
呼び出し元に戻り、変数appleのquantityの値を出力すると10となっています。
呼び出した先で値を変更した場合に、呼び出し元にもその影響が出ています。


続いて値渡し。

ソース

public class Main {
	public static void main(String[] args) {
		int value = 5;

		change(value);
		System.out.println(value);
	}

	public static void change(int value) {
		value = 10;
	}
}

実行結果

5

int型の変数valueを5で初期化し、changeメソッドで引数に渡されたvalueを10に変更します。
呼び出し元に戻り変数valueの値を出力すると5のままです。

以上の結果から、参照渡しと値渡しでは呼び出し元に影響があるかないかに差が出ることがわかります。
Apple型の変数の値を変更した場合は呼び出し元でも変更されています。
しかしint型の変数の値を変更しても呼び出し元では変更前の値のままです。

実は参照渡しと値渡しは渡す変数の型で決まります。
int型などのプリミティブ型を渡す場合は値渡しになります。
そしてプリミティブ型以外のオブジェクト型を渡す場合は参照渡しとなります。
ただし、StringやIntegerなどのラッパークラスはオブジェクト型ではありますが値渡しとなります。


つまり値渡しであるint型などを値を変更したい場合は、以下のように戻り値を利用する必要があります。

ソース

public class Main {
	public static void main(String[] args) {
		int value = 5;

		value = change(value);
		System.out.println(value);
	}

	public static int change(int value) {
		return 10;
	}
}

実行結果

10

さらに言うと、参照渡しであっても戻り値で明示的に設定する方が良いのではないかと思います。
というのも、「値を変えている」というのが他の人から見てもわかりやすいからです。


逆に、オブジェクト型の値をメソッド内で変更しても呼び出し元に影響させたくない場合。
その場合は複製を作成し、複製されたものを変更するようにします。

ソース

public class Main {
	public static void main(String[] args) {
		Apple apple = new Apple(5);

		change(apple);
		System.out.println(apple.quantity);
	}

	public static void change(Apple apple) {
		Apple cloneApple = apple.clone();
		cloneApple.quantity = 10;
	}
}

class Apple implements Cloneable {
	String name;
	int quantity;

	public Apple() {
	}

	public Apple(int quantity) {
		this.name = "apple";
		this.quantity = quantity;
	}

	@Override
	public Apple clone() {
		Apple apple = new Apple();

		try {
			apple = (Apple) super.clone();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return apple;
	}
}

実行結果

5

cloneメソッドで複製を作成し、複製された変数を変更しても複製元には影響がありません。

ちなみにcloneメソッドを使用するにはCloneableインターフェースを実装する必要があります。
また、CloneNotSupportedExceptionを投げる可能性があるのでAppleクラスでcloneメソッドをオーバーライドしtry~catchで囲みます。

実際に複製を作ってまで変数に変更を加えることがあるのか?という疑問もあると思います。
体験談としては、Listをソートして取り出したいが、そのリスト自体はソートされてほしくないようなパターンの時に使いました。
Listのコピーは以下のように簡単に作成できます。

List<String> list = new ArrayList<>();

List<String> copyList = new ArrayList<String>(list);

変数copyListは変数listの複製であるため、copyListを変更してもlistに影響はありません。

今回は以上になります。