「Java Interview Question Series」:長い知識と非常に興味深いコラム。詳細な掘り起こし、ソースコードの分析、原則の要約、写真とテキストの組み合わせ、公式アカウントでの一連の記事の作成、インタビューのレベルの引き上げなど [Program New Vision]に引き続きご注目ください。これは第7章です。
キーポイント:Javaメソッドは値渡しですか、それとも参照渡しですか?
この問題は、テクニカルディスカッションフォーラムで議論されています。多くの上級開発者であっても、ジュニアスタッフにとっては非常に混乱します。この記事では、基本的な原則を探り、最後に複雑さを単純化して、値で渡されるのか、1つまたは2つの文で参照で渡されるのかを理解できるようにします。
面接の質問
一般的なインタビューの質問については、次のコードを実行して、対応する印刷結果を表示します。
@Test
public void test1() {
String[] arr = {"关注", "程序", "新视界"};
String name = "二师兄";
System.out.println("before change : arr=" + Arrays.toString(arr));
System.out.println("before change : name=" + name);
change(arr, name);
System.out.println("after change : arr=" + Arrays.toString(arr));
System.out.println("after change : name=" + name);
}
public void change(String[] arr, String name) {
arr[0] = "公众号";
name = new String("Steven");
System.out.println("in change method : arr=" + Arrays.toString(arr));
System.out.println("in change method : name=" + name);
}
最初に3つの場所に出力される結果の違いについて考え、次にプログラムを実行した後の結果を比較します。
before change : arr=[关注, 程序, 新视界]
before change : name=二师兄
in change method : arr=[公众号, 程序, 新视界]
in change method : name=Steven
after change : arr=[公众号, 程序, 新视界]
after change : name=二师兄
changeメソッドで名前が変更されたのに、最終結果が変更されないのはなぜですか?そして、arrは0番目の位置のコンテンツを変更しましたが、正常に変更しましたか?最初のパラメーターは参照によって渡され、2番目のパラメーターは値によって渡されますか?
上記の質問を1つずつ解決し、最終的な結論をまとめましょう。
値渡しと参照渡し
まず、値の受け渡しと参照の受け渡しの概念を理解しましょう。
値渡しとは、関数が呼び出されると、実際のパラメーターがコピーされて関数に渡され、実際のパラメーターに影響を与えることなく関数内でパラメーターが変更されることを意味します。
参照渡しとは、関数を呼び出すときに実際のパラメーターのアドレスを関数に直接渡すことを指します。関数内のパラメーターを変更すると、実際のパラメーターに影響します。
概念を通じて、値の受け渡しと参照の受け渡しの本質的な違いを引き出すことができます。つまり、実際のパラメーターの値がコピーされるか、直接使用されるかです。
基本タイプと文字列転送プロセス
この問題についてはしばしば誤解があります。値の転送と参照の転送の違いは転送の内容であり、値の場合は値の転送です。参照の場合は、参照によって渡されます。
上記の理解は誤りであることに注意してください。値の受け渡しと参照の受け渡しの兆候は、オブジェクトタイプに関連しているように見えますが、本質的には無関係です。
最初に、基本的な型と、通常メモリに存在するString型の形式を見てみましょう。このシリーズの以前の記事では、より多くの形式のプレゼンテーションが何度も言及されています。
要約すると、メソッドの基本型の参照と値はスタックに割り当てられ、文字列型の参照はスタックにあり、値はヒープ(ヒープまたは文字列定数プール)にあります。
メソッドにパラメーターとして渡されたときの最初の基本型(例としてintを取る)のメモリー構造を見てみましょう。
@Test
public void test2(){
int a = 10;
change(a);
}
public void change(int b) {
b = 2;
}
まず、メソッドが呼び出されない場合、変数aのメモリ配分は上図のようになります。changeメソッドが呼び出される場合、int型の変数aとbは、次のようにメモリ内にあります。
明らかに、メソッドの仮パラメーターbの場合、aのデータのコピーがスタックに複製されます。この時点で、bを再割り当てすると、メモリは次のように変化します。
また、(change)メソッドの実行が完了すると、スタックの内容が消えることがわかります。つまり、changeメソッドはプロセス全体でaの値をコピーしただけで、メソッド内の変数bの操作はaの元の値に影響を与えませんでした。
上記の概念と比較すると、このプロセスが価値の移転であることが簡単にわかります。Stringオブジェクトのメモリ構造の変更を見てみましょう。
@Test
public void test3(){
String name = "Tom";
change(name);
}
public void change(String str) {
str = new String("Steven");
}
最初のメモリ構造は最初の図と同じで、名前はスタックに格納され、値Tomは文字列定数プールに格納されます。changeメソッドが呼び出されると、メモリ構造は次のようになります。
メソッドを呼び出すと、changeメソッドの仮パラメーターstrは、変数名に格納されている参照アドレスをコピーし、文字列 "Tom"も指します。
メソッドでstr割り当てを実行すると、メモリ構造は次のようになります。
この時点で、メソッドで実行された代入操作はstr変数に格納されている文字列のアドレスを変更するだけで、元の名前変数が指す文字列には影響を与えないことがわかります。
上記は基本タイプの変更に関するもので、Stringクラスは値転送の要件を満たしています。実際のパラメーターをコピーし、元の値に影響を与えずにコピーしたパラメーター値を変更します。文字列型にコピーされる実際の値のみが、変数に格納されている参照アドレスです。
友人は言った:いいえ、私はオブジェクトを渡してから、そのsetメソッドを呼び出してオブジェクトのコンテンツを変更します!
それでは、参照型のメモリ構造図を見てみましょう。
参照型の値の受け渡し
配列も参照型であることは誰もが知っていますが、より明確にするために、確認のためにここに新しいUserオブジェクトを作成します。
@Test
public void test4() {
User user = new User();
user.setName("Tom");
change(user);
}
public void change(User paramUser) {
paramUser.setName("Steven");
}
class User{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
changeメソッドを実行する前のメモリ構造図は次のとおりです。
changeメソッドでsetメソッドを実行すると、メモリ構造は次のようになります。
paramUserパラメータは、userパラメータのアドレスもコピーするため、ヒープ内のUserオブジェクトも指していることがわかります。したがって、一部の友人は、Userオブジェクトが実際に変更されたと言っています!
気になるのは、オブジェクトの内部構造が変更されているかどうかではなく、コピーされた元のユーザーパラメータの値が変更されているかどうかです。上の図から、元のユーザーの値(参照アドレス)がまったく変更されていないことがはっきりとわかります。
新しいオブジェクトをparamUserに直接割り当てると、元のユーザー変数の値が変更されますか?さらに検証するために、changeメソッドの内容を変更してみましょう。
@Test
public void test4() {
User user = new User();
user.setName("Tom");
change(user);
}
public void change(User paramUser) {
paramUser = new User();
paramUser.setName("Steven");
}
changeメソッドを実行する前のメモリ構造は上記の構造と同じですが、実行後、メモリ構造は新たに変更されています。
参照によって渡される場合、paramUserを再割り当てすると、元のユーザーに対応する値が確実に変更されます。しかし、ここでは、割り当て後、paramUserがヒープ内の新しいオブジェクトを指すだけで、元のユーザー値には影響しないことは明らかです。
概要
上記のステップバイステップの分析とデモンストレーションを通して、私たちは明確に見ることができます:それが基本タイプである場合、メソッドが渡されるときに(スタック内の)基本タイプの参照と値がコピーされ、参照タイプである場合、コピーは(スタック内)参照アドレス。
つまり、渡されるタイプに関係なく、実際の値を直接転送する参照転送ではなく、最終的にコピー操作が実行されます。したがって、Javaでは、本質的に値の受け渡しのみが行われます。つまり、Javaのパラメーターの受け渡しでは、パラメーター自体ではなく、そのコピーのみが渡されます。
最後に、上記の分析プロセスを通じて、JVMのメモリ構造を理解した後、多くの不可解な問題を簡単に解決できることがわかりましたか?だから、急いでもっと学びましょう。
元のリンク:「インタビューの質問シリーズNo. 7:Javaメソッドは値渡しですか、それとも参照渡しですか?》