Javaの値渡しと参照渡しを低レベル原理から理解する

この記事で学ぶこと

  • パラメータ渡しの方式
    • 値渡し
    • 参照渡し
    • ポインタ渡し
  • Javaの値渡しと参照渡しの完全理解
    • 低レベルからの分析
    • 値渡しにおけるコピー動作

Javaのパラメータ渡しの例

以下のmainメソッドの出力結果を求めてみましょう。結果がわかる場合はこのセクションを飛ばしても構いません。

public static void main(String[] args) {
    int value = 10;
    modifyPrimitive(value);
    System.out.printf("プリミティブ型メソッド終了後=>value=%d \n", value);
    
    User user = new User();
    user.username = "山田";
    modifyReference(user);
    System.out.printf("参照型メソッド終了後=>user.username=%s \n", user.username);
}

public static void modifyPrimitive(int value){
    value = 20;
}

public static void modifyReference(User user){
    user.username = "佐藤";
}

public static class User{
    public String username;
}

出力結果

プリミティブ型メソッド終了後=>value=10 
参照型メソッド終了後=>user.username=佐藤 

値渡しとは?

値渡しとは、メソッド呼び出し時に実引数のコピーを渡す方式です。これにより、メソッド内でパラメータを変更しても、呼び出し元の実引数には影響が及びません。

Javaの基本データ型には、整数型(4種類)、浮動小数点型(2種類)、文字型(1種類)、論理型(1種類)があります。

代入演算子(=)によって、変数の値が呼び出し元のスコープから呼び出されたメソッドのスコープに渡されます。

基本データ型の場合、コピーされるのは値そのものであり、変数のメモリアドレスではありません。そのため、メソッド内で変数が変更されても、呼び出し元に戻った時点では、変数の値はコピーされた時点の値のままです。

public static void main(String[] args) {
    int num = 100;
    Integer numObj = 100;
    char letter = 'A';
    Character charObj = 'A';
    modifyPrimitives(num, numObj, letter, charObj);
    System.out.printf("メソッド終了後=>num=%d, numObj=%d, letter=%c, charObj=%c \n", num, numObj, letter, charObj);
}

public static void modifyPrimitives(int num, Integer numObj, char letter, Character charObj){
    num = 200;
    numObj = 200;
    letter = 'B';
    charObj = 'B';
    System.out.printf("メソッド内=>num=%d, numObj=%d, letter=%c, charObj=%c \n", num, numObj, letter, charObj);
}
メソッド内=>num=200, numObj=200, letter=B, charObj=B 
メソッド終了後=>num=100, numObj=100, letter=A, charObj=A 

参照渡しとは?

参照渡しとは、メソッド呼び出し時に実引数のアドレスを渡す方式です。これにより、メソッド内でパラメータに対して行われた変更は、呼び出し元の実引数に影響を及ぼします。

Javaでは、参照渡しに類似したものとして参照型の渡し方(配列、Objectクラスなど)があります。代入演算子(=)によって、変数が参照するオブジェクトが呼び出し元のスコープから呼び出されたメソッドのスコープに渡されます。

参照型の場合、コピーされるのはオブジェクトへの参照であり、オブジェクトそのものではありません。そのため、メソッド内でオブジェクトが変更された場合、呼び出し元に戻った時点でも、参照しているオブジェクトは呼び出し時と同じオブジェクトですが、その内容は変更されている可能性があります。例えば、オブジェクトが他のオブジェクトをメンバーとして含む場合、値渡しのプロセスではオブジェクトの参照のみがコピーされ、メンバーオブジェクトはコピーされません。これは、メソッド内でオブジェクトの内部メンバーを変更した場合、呼び出し元に戻った時点でそのメンバーオブジェクトが最新でない可能性があることを意味します。

値渡し:参照データ型

public static void main(String[] args) {
    int[] numbers = new int[1];
    char[] letters = new char[1];
    List<Integer> list = new ArrayList<>();
    User user = new User();
    
    numbers[0] = 10;
    letters[0] = 'A';
    list.add(10);
    user.username = "山田";
    
    System.out.printf("メソッド実行前=> " +
            "\n numbers[0]=%d, letters[0]=%c, list.get(0)=%d, user.username=%s \n", 
            numbers[0], letters[0], list.get(0), user.username);
    
    modifyReferences(numbers, letters, list, user);
    
    System.out.printf("メソッド終了後=> " +
            "\n numbers[0]=%d, letters[0]=%c, list.get(0)=%d, user.username=%s \n", 
            numbers[0], letters[0], list.get(0), user.username);
}

public static void modifyReferences(int[] numbers, char[] letters, List<Integer> list, User userCopy){
    numbers[0] = 20;
    letters[0] = 'B';
    list.set(0, 20);
    userCopy.username = "佐藤";
    
    System.out.printf("メソッド内=> " +
            "\n numbers[0]=%d, letters[0]=%c, list.get(0)=%d, user.username=%s \n", 
            numbers[0], letters[0], list.get(0), userCopy.username);
}

public static class User{
    public String username;
}
メソッド実行前=> 
 numbers[0]=10, letters[0]=A, list.get(0)=10, user.username=山田 
メソッド内=> 
 numbers[0]=20, letters[0]=B, list.get(0)=20, user.username=佐藤 
メソッド終了後=> 
 numbers[0]=20, letters[0]=B, list.get(0)=20, user.username=佐藤 

低レベル原理からの理解

スタックとヒープの観点から説明すると、Javaは値渡しです。Javaでは、変数を呼び出し元のスコープから呼び出されたメソッドのスコープに渡す際、変数のメモリアドレスではなく、変数の値(基本型の場合は値、オブジェクトの場合は参照)が渡されます。

値渡し(基本データ型の渡し)の図解

基本データ型の場合、Javaはスタック上にメモリを割り当て、渡されるのは単なるクローンデータです。そのため、互いに影響を及ぼしません。

参照型の渡しの図解

参照データ型の場合、Javaはほとんどの場合ヒープ上にメモリを割り当て、スタック上のuserは単なる参照です。userとuserCopyは2つの異なる参照ですが、両方とも同じアドレスUser@12345を指しているため、これらの参照を使ってオブジェクト内部を変更すると、その影響は同じになります。

理解度チェック

public static void main(String[] args) {
    User user = new User();
    user.username = "山田";
    System.out.printf("メソッド実行前=> user.username=%s \n", user.username);
    modifyReference(user);
    System.out.printf("メソッド終了後=> user.username=%s \n", user.username);
}

public static void modifyReference(User user){
    user = new User();
    user.username = "佐藤";
}

public static class User{
    public String username;
}

上記のコードで【メソッド終了後=>user.username=】には何が出力されるでしょうか?

タグ: Java 値渡し 参照渡し メモリ管理

5月22日 08:57 投稿