C++ と Java を学習したことがある学生は、値渡しと参照渡しの 2 つの方法に苦労することは間違いないと思います. 私は以前に愚かで混乱していたので、今日は 2 つの違いについて話します.
まず、値渡しと参照渡しを理解する前に、仮パラメータと実パラメータの概念を理解する必要があります。
仮パラメータと実パラメータ
Formal parameters refer to the parameters we use when defined a function. その目的は、関数呼び出し時に渡されたパラメーターを受け取ることであり、呼び出し時に渡されたパラメーターは 実パラメーターと呼ばれます。
コードを見ると、より直感的です。
public static void main(String[] args){
int num = 1;
this.method(num);
System.err.println(num);
}
public static void method(int num){
num = 2;
}
値渡しと参照渡し
最初に彼らのコンセプトについて話しましょう。
値渡し: 関数呼び出し時に実パラメータの元のデータが関数にコピーされ、関数内でパラメータが変更された場合、実パラメータは影響を受けないことを意味します。つまり、値渡しは仮パラメータのみを変更し、実際のパラメータには影響しません。
参照渡し: 関数を呼び出すときに、実際のパラメーターのアドレスが関数に渡されるため、関数内のパラメーターの変更が実際のパラメーターに影響することを意味します。
ここで注意したいのは、単純に入力パラメータの型で値渡しか参照渡しかを判断するのではなく、渡されたパラメータが値渡しであるとは限らず、参照型であるパラメータパラメータが参照渡しであるとは限らないということです。値渡しか参照渡しかの判断は、渡されるパラメータの型とは関係ありません。
上記の状況をカバーする 3 つの例を次に示します。
入力パラメータが基本データ型の場合
public static void main(String[] args){
int num = 1;
System.out.println("实参修改前:" + num);
this.method(num);
System.out.println("实参修改后:" + num);
}
public static void method(int num){
System.out.println("形参修改前:" + num);
num = 2;
System.out.println("形参修改后:" + num);
}
印刷結果:
实参修改前:1
形参修改前:1
形参修改后:2
实参修改后:1
基本データ型が渡されると、関数内で仮パラメーターのみが変更され、実パラメーターの値は影響を受けないことがわかります。
値による受け渡しは、単に実パラメーターを仮パラメーターに渡すのではなく、実パラメーターがコピーをコピーし、そのコピーを新しいパラメーターに渡すことを理解する必要があります。次の図は、パラメーターの受け渡しのプロセスを示しています。
図では、num が実パラメータで、コピー temp が作成され、仮パラメータ値が渡されます。値を変更しても、実パラメータ num には影響しません。
渡された型は参照型です
public class User {
private int age;
private String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public User(int age, String name) {
this.age = age;
this.name = name;
}
public User() {
}
@Override
public String toString() {
return "User{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
public class TestUser {
public static void main(String[] args) {
User user = new User(18, "zhangsan");
System.out.println("修改对象前:"+user);
changeUser(user);
System.out.println("修改对象后:"+user);
}
private static void changeUser(User user) {
user.setAge(20);
user.setName("lisi");
}
}
印刷結果:
修改对象前:User{age=18, name='zhangsan'}
修改对象后:User{age=20, name='lisi'}
渡されたユーザー オブジェクトの属性値が変更されていることがわかります。ユーザー オブジェクトはヒープに格納されるため、その参照はスタックに格納され、そのパラメーター転送図は次のようになります。
user は実パラメータとしてオブジェクトへの参照であり、コピー temp を作成し、それを仮パラメータ user1 に渡します。ただし、実際に操作するのはヒープ メモリ内の同じ User オブジェクトです。そのため、オブジェクト内容の変更は実パラメータ user にも反映されます。
転送型は String 型 (Integer などの基本型のラッパークラスは同等)
public class TestStr {
public static void main(String[] args) {
String str = new String("zhangsan");
System.out.println("字符串修改前:"+str);
changeStr(str);
System.out.println("字符串修改后:"+str);
}
private static void changeStr(String str) {
str = "lisi";
}
}
印刷結果:
字符串修改前:zhangsan
字符串修改后:zhangsan
ねえ、これが事実かどうかを確認するために少し混乱しています。2番目のケースによると、渡されたパラメーターが参照型の場合、オブジェクトの内容を変更できませんか? String も参照型です. なぜここで変更しないのですか?
渡されたパラメーターは参照型であることをもう一度強調しておきますが、これは参照によって渡されるという意味ではなく、実際には値によって渡されます。このときのリーシは、上記の張山と同じ物ではありません。理解するために絵を描いてください (次のスタック図には StringTable の知識が含まれており、次の図は jdk1.8 に基づいて描かれています)。
図中、str は実パラメータであるオブジェクト zhangsan の参照であり、コピー temp が作成され、仮パラメータ str1 に渡されます。このとき、新しいオブジェクト lisi が作成され、仮パラメーター str1 はこのオブジェクトを指しますが、元の実パラメーター str はまだ zhangsan を指しています。したがって、仮パラメータの内容を変更しても、実パラメータの内容には影響しません。したがって、2 つの印刷結果は張山です。
3 番目のケースと 2 番目のケースはすべて参照型の変数ですが、処理方法が異なります。3 番目のケースでは、新しいオブジェクトを作成し、仮パラメータを新しいオブジェクトに向けます。2 番目のケースでは、新しいオブジェクトを作成せず、操作は同じオブジェクトのままです。上記の changeUser メソッドを少し変更すると、次のことがわかります。
private static void changeUser(User user) {
//添加一行代码,创建新的User对象
user = new User();
user.setAge(20);
user.setName("lisi");
}
上記のコードを実行すると、変更前と変更後の最終的な印刷内容がまったく同じであることに驚かれることでしょう。このケースは 3 番目のケースに相当します。ここでは、仮パラメーターが指すオブジェクトと実パラメーター参照が異なるオブジェクトであるためです。したがって、仮パラメータ オブジェクトの内容を変更しても、実パラメータの内容には影響しません。
修改对象前:User{age=18, name='zhangsan'}
修改对象后:User{age=18, name='zhangsan'}
要約する
上記の 3 つの例から、Java が参照渡しではなく値渡しのみを行う理由が理解できます。値渡しでは、渡されたパラメーターの型が値型か参照型かに関係なく、仮パラメーターのコピーが呼び出し履歴に作成されます。違いは、値型の場合、コピーされるのは元の値全体のコピーであることです。参照型の場合、オブジェクトの参照のみがコール スタックに格納されるため、元のオブジェクトではなく、この参照のみがコピーされます。
最後に、パラメータを参照型またはオブジェクトとして渡す場合、それが参照パスであるとは限らないことをもう一度強調しておきます。参照渡しは、パラメーターの型を説明するために使用されるわけではありません。「参照」という言葉自体と混同しないでください。
- パラメーターを渡す場合、実パラメーターのコピーがコピーされてから、仮パラメーターに渡されます。(値渡し)
- 関数では、実パラメータが指すオブジェクトの内容が変更された場合にのみ、実パラメータが影響を受けます。上記の 3 番目のケースでは、仮パラメーターが指すオブジェクトのみが実際に変更されるため、実パラメーターは影響を受けません。