Javaのパラメータ受け渡しメカニズムとしての値渡し
1. 序章
メソッドに引数を渡す最も一般的な2つのモードは、「値による受け渡し」と「参照による受け渡し」です。 さまざまなプログラミング言語がこれらの概念をさまざまな方法で使用します。Javaに関する限り、すべてが厳密に値渡しです。
このチュートリアルでは、Javaがさまざまな型の引数を渡す方法を説明します。
2. 値渡しと参照渡し
パラメータを関数に渡すためのさまざまなメカニズムのいくつかから始めましょう。
- 価値
- 参照
- 結果
- 値の結果
- 名前
最新のプログラミング言語で最も一般的な2つのメカニズムは、「Pass-by-Value」と「Pass-by-Reference」です。 先に進む前に、まずこれらについて説明しましょう。
2.1. 値渡し
パラメータが値渡しの場合、呼び出し元と呼び出し先のメソッドは、相互にコピーされた2つの異なる変数を操作します。 一方の変数を変更しても、もう一方の変数は変更されません。
これは、メソッドの呼び出し中に、呼び出し先メソッドに渡されるパラメーターが元のパラメーターのクローンになることを意味します。呼び出し先メソッドで行われた変更は、呼び出し元メソッドの元のパラメーターには影響しません。
2.2. 参照渡し
パラメータが参照渡しの場合、呼び出し元と呼び出し先は同じオブジェクトを操作します。
これは、変数が参照渡しの場合、オブジェクトの一意の識別子がメソッドに送信されることを意味します。パラメーターのインスタンスメンバーに変更を加えると、元の値に変更が加えられます。
3. Javaでのパラメータの受け渡し
プログラミング言語の基本的な概念は、「値」と「参照」です。 Javaでは、
Javaの引数は、常に値で渡されます。 メソッドの呼び出し中に、値または参照に関係なく、各引数のコピーがスタックメモリに作成され、メソッドに渡されます。
プリミティブの場合、値はスタックメモリ内にコピーされ、呼び出し先メソッドに渡されます。 非プリミティブの場合、スタックメモリ内の参照は、ヒープに存在する実際のデータを指します。 オブジェクトを渡すと、スタックメモリ内の参照がコピーされ、新しい参照がメソッドに渡されます。
いくつかのコード例を使用して、これが実際に動作していることを見てみましょう。
3.1. プリミティブタイプの受け渡し
Javaプログラミング言語は、8つのプリミティブデータ型を備えています。
これらの仮パラメーターの有効期間は、そのメソッドが実行されている間のみ存続し、戻ると、これらの仮引数はスタックからクリアされて破棄されます。
コード例を使用して、それを理解してみましょう。
public class PrimitivesUnitTest {
@Test
public void whenModifyingPrimitives_thenOriginalValuesNotModified() {
int x = 1;
int y = 2;
// Before Modification
assertEquals(x, 1);
assertEquals(y, 2);
modify(x, y);
// After Modification
assertEquals(x, 1);
assertEquals(y, 2);
}
public static void modify(int x1, int y1) {
x1 = 5;
y1 = 10;
}
}
これらの値がメモリにどのように格納されているかを分析して、上記のプログラムのアサーションを理解してみましょう。
- mainメソッドの変数「x」および「y」はプリミティブ型であり、それらの値はスタックメモリに直接格納されます。
- メソッドmodify()を呼び出すと、これらの各変数の正確なコピーが作成され、スタックメモリの異なる場所に格納されます。
- これらのコピーに変更を加えると、それらにのみ影響し、元の変数は変更されません。
3.2. オブジェクト参照の受け渡し
Javaでは、すべてのオブジェクトは内部のヒープスペースに動的に格納されます。 これらのオブジェクトは、参照変数と呼ばれる参照から参照されます。
Primitivesとは対照的に、Javaオブジェクトは2つのステージに格納されます。 参照変数はスタックメモリに格納され、参照しているオブジェクトはヒープメモリに格納されます。
オブジェクトが引数として渡されるときはいつでも、元の参照変数と同じヒープメモリ内のオブジェクトの場所を指す参照変数の正確なコピーが作成されます。
この結果、メソッド内の同じオブジェクトに変更を加えると、その変更は元のオブジェクトに反映されます。ただし、渡された参照変数に新しいオブジェクトを割り当てると、元のオブジェクトには反映されません。
コード例を使用して、これを理解してみましょう。
public class NonPrimitivesUnitTest {
@Test
public void whenModifyingObjects_thenOriginalObjectChanged() {
Foo a = new Foo(1);
Foo b = new Foo(1);
// Before Modification
assertEquals(a.num, 1);
assertEquals(b.num, 1);
modify(a, b);
// After Modification
assertEquals(a.num, 2);
assertEquals(b.num, 1);
}
public static void modify(Foo a1, Foo b1) {
a1.num++;
b1 = new Foo(1);
b1.num++;
}
}
class Foo {
public int num;
public Foo(int num) {
this.num = num;
}
}
上記のプログラムのアサーションを分析してみましょう。 同じ値1を持つmodify()メソッドでオブジェクトaとbを渡しました。 最初、これらのオブジェクト参照は、ヒープスペース内の2つの異なるオブジェクトの場所を指しています。
これらの参照aおよびbがmodify()メソッドで渡されると、これらの参照a1およびのミラーコピーが作成されます。 ] b1 これは、同じ古いオブジェクトを指します。
modify()メソッドで、参照 a1 を変更すると、元のオブジェクトが変更されます。 ただし、参照 b1、用に新しいオブジェクトを割り当てました。 つまり、ヒープメモリ内の新しいオブジェクトを指していることになります。
b1 に加えられた変更は、元のオブジェクトには何も反映されません。
4. 結論
この記事では、プリミティブと非プリミティブの場合にパラメーターの受け渡しがどのように処理されるかを確認しました。
Javaでのパラメータの受け渡しは、常に値渡しであることがわかりました。 ただし、プリミティブとオブジェクトのどちらを扱っているかによって、コンテキストは変わります。
- プリミティブ型の場合、パラメーターは値渡しです
- オブジェクトタイプの場合、オブジェクト参照は値渡しです
この記事で使用されているコードスニペットは、GitHubのにあります。