Javaは「参照渡し」または「値渡し」ですか?


6580

Javaは参照渡しであるといつも思っていました。

しかし、そうではないと主張するいくつかのブログ投稿(たとえば、このブログ)を見ました。

彼らの違いを理解していないと思います。

説明は何ですか?


706
この問題に関する混乱の多くは、「参照」という用語の定義が人によって異なるという事実に関係していると思います。C ++のバックグラウンドから来た人々は、「参照」はC ++での意味を意味する必要があると想定し、Cのバックグラウンドからの人々は、「参照」は言語の「ポインタ」と同じであると想定します。Javaが参照渡しであると言うのが正しいかどうかは、実際には「参照」が何を意味するかに依存します。
重力

119
評価戦略の記事にある用語を常に使用するようにしています。用語アウト記事のポイントは、コミュニティによって大きく異なるにもかかわらず、それは、ことに留意すべきであるために意味論と強調call-by-valueしてはcall-by-reference非常に重要な方法で異なります。(個人的にはcall-by-object-sharing、最近call-by-value[-of-the-reference]のセマンティクスを高レベルで説明し、基礎となる実装call-by-valueであるとの競合を引き起こさないため、最近ではを使用することを好みます。)

59
@重力:巨大な看板などにコメントを書いてもらえますか?それが一言で言えば問題全体です。そして、それはこの全体が意味論であることを示しています。リファレンスの基本的な定義に同意しない場合、この質問への回答に同意しません:)
MadConan

30
混乱は、「参照渡し」と「参照セマンティクス」の違いだと思います。Javaは参照セマンティクスを持つ値渡しです。
2014年

11
@Gravity、あなたがしている間、絶対に C ++から来ている人たちで正しい本能的に、私は個人的に体がより多くの「によって」に埋設されていると信じて、用語「参照」に関するさまざまな直感がセットされています。"Pass by"は、Javaの "Passing a"とはまったく異なる点で混乱しています。C ++では、口語的にはそうではありません。C ++では、「参照を渡す」と言うことができswap(x,y)テストに合格すると理解されています

回答:


5842

Javaは常に値渡しです。残念ながら、オブジェクトの値を渡すと、オブジェクトへの参照が渡されます。これは初心者には混乱します。

こんなふうになります:

public static void main(String[] args) {
    Dog aDog = new Dog("Max");
    Dog oldDog = aDog;

    // we pass the object to foo
    foo(aDog);
    // aDog variable is still pointing to the "Max" dog when foo(...) returns
    aDog.getName().equals("Max"); // true
    aDog.getName().equals("Fifi"); // false
    aDog == oldDog; // true
}

public static void foo(Dog d) {
    d.getName().equals("Max"); // true
    // change d inside of foo() to point to a new Dog instance "Fifi"
    d = new Dog("Fifi");
    d.getName().equals("Fifi"); // true
}

上記の例でaDog.getName()はまだ戻り"Max"ます。aDog内の値mainは、オブジェクト参照が値によって渡されるため、関数fooで変更されませんDog "Fifi"。参照によって渡された場合、aDog.getName()in mainはへ"Fifi"の呼び出し後に戻りfooます。

同様に:

public static void main(String[] args) {
    Dog aDog = new Dog("Max");
    Dog oldDog = aDog;

    foo(aDog);
    // when foo(...) returns, the name of the dog has been changed to "Fifi"
    aDog.getName().equals("Fifi"); // true
    // but it is still the same dog:
    aDog == oldDog; // true
}

public static void foo(Dog d) {
    d.getName().equals("Max"); // true
    // this changes the name of d to be "Fifi"
    d.setName("Fifi");
}

上記の例でFifiは、はfoo(aDog)内にオブジェクトの名前が設定されているため、への呼び出し後の犬の名前ですfoo(...)。でfoo実行さdれる操作は、すべての実用的な目的のためにで実行されるようなものですが、変数自体の値を変更するaDogことはできませんaDog


263
内部の詳細で問題を少し混乱させていませんか?「オブジェクトへの内部ポインタの値」を意味すると仮定すれば、「参照の受け渡し」と「参照の値の受け渡し」の間に概念的な違いはありません。
izb 2008

383
しかし、微妙な違いがあります。最初の例を見てください。純粋に参照渡しの場合、aDog.nameは「Fifi」になります。そうではありません-取得している参照は、上書きされた場合に関数を終了するときに復元される値の参照です。
erlando 2008

300
@Lorenzo:いいえ、Javaではすべてが値によって渡されます。プリミティブは値で渡され、オブジェクト参照は値で渡されます。オブジェクト自体がメソッドに渡されることはありませんが、オブジェクトは常にヒープ内にあり、オブジェクトへの参照のみがメソッドに渡されます。
Esko Luontola、2009

294
オブジェクトの通過を視覚化する良い方法での私の試み:バルーンを想像してください。fxnの呼び出しは、2番目の文字列をバルーンに結び付け、行をfxnに渡すようなものです。parameter = new Balloon()はその文字列をカットして新しいバルーンを作成します(ただし、これは元のバルーンには影響しません)。ただし、parameter.pop()は文字列をたどって同じ元のバルーンに移動するため、それでもポップします。Javaは値渡しですが、渡される値は深くありません。つまり、プリミティブまたはポインタなど、最高レベルです。これを、オブジェクトが完全に複製されて渡される、値による深い受け渡しと混同しないでください。
dhackner

150
混乱しているのは、オブジェクト参照が実際にはポインタであることです。初めにSUNはそれらをポインタと呼びました。それからマーケティングは「ポインター」が悪い言葉であることを知らせました。しかし、NullPointerExceptionに「正しい」命名法が表示されています。
ファルケン教授の契約が

3035

私はあなたが私の記事を参照したことに気づきました。

Java仕様では、Javaのすべてが値渡しであると述べています。Javaには「参照渡し」などはありません。

これを理解するための鍵は、

Dog myDog;

犬ではありません。それは実際には犬へのポインターです。

つまり、あなたが持っているときです

Dog myDog = new Dog("Rover");
foo(myDog);

基本的に、作成したオブジェクトのアドレスをメソッドに渡します。Dogfoo

(基本的に、Javaポインターは直接アドレスではないためですが、そのように考えるのが最も簡単です)

Dogオブジェクトがメモリアドレス42にあるとします。これは、メソッドに42を渡すことを意味します。

メソッドが次のように定義されている場合

public void foo(Dog someDog) {
    someDog.setName("Max");     // AAA
    someDog = new Dog("Fifi");  // BBB
    someDog.setName("Rowlf");   // CCC
}

何が起こっているのか見てみましょう。

  • パラメータsomeDogは値42に設定されます
  • 「AAA」行
    • someDogDogitが指す(Dogアドレス42のオブジェクト)に続きます
    • それDog(アドレス42にある人)は名前をMaxに変更するように求められます
  • 「BBB」行
    • 新しいものDogが作成されます。彼が住所74にいるとしましょう
    • パラメータsomeDogを74に割り当てます
  • 「CCC」行
    • someDogは、Dogそれが指すit(Dogアドレス74のオブジェクト)に従います。
    • それDog(アドレス74にある人)は名前をRowlfに変更するように求められます
  • その後、私たちは戻ります

次に、メソッドの外で何が起こるかを考えてみましょう。

DID myDogの変更を?

鍵があります。

これmyDogポインタであり、実際Dogではないことを念頭に置いて、答えはノーです。myDog値はまだ42です。それはまだオリジナルを指していますDog(ただし、「AAA」という行があるため、その名前は「Max」になりました-まだ同じDogです; myDogの値は変更されていません。)

住所をフォローして、住所の最後にあるものを変更することは完全に有効です。ただし、変数は変更されません。

JavaはCとまったく同じように動作します。ポインターを割り当て、メソッドにポインターを渡し、メソッド内でポインターをたどり、ポイントされたデータを変更できます。ただし、そのポインタが指す場所を変更することはできません。

C ++、Ada、Pascal、および参照渡しをサポートするその他の言語では、渡された変数を実際に変更できます。

Javaに参照渡しのセマンティクスfoomyDogあった場合、上で定義したメソッドは、someDogBBB行に割り当てたときに指し示していた場所を変更します。

参照パラメーターは、渡された変数のエイリアスであると考えてください。そのエイリアスが割り当てられると、渡された変数も同様に割り当てられます。


164
これが、「Javaにはポインタがない」という一般的なリフレインが誤解を招く理由です。
Beska

134
あなたは間違っている、イモ。「myDogはポインターであり、実際の犬ではないことを念頭に置いて、答えはNOです。myDogの値はまだ42です。それでも元の犬を指しています。」myDogの値は42ですが、その名前引数には// AAA行の「Rover」ではなく「Max」が含まれています。
Özgür

180
このように考えてください。「annArborLocation」と呼ばれる紙に誰かがミシガン州アナーバー(私の故郷、GO BLUE!)の住所を書いています。「myDestination」と呼ばれる紙にコピーします。「myDestination」まで車で行き、木を植えることができます。その場所で都市について何かを変更した可能性がありますが、どちらの紙に書かれたLAT / LONも変更しません。「myDestination」でLAT / LONを変更できますが、「annArborLocation」は変更されません。それは役に立ちますか?
スコットスタンフィールド

43
@Scott Stanchfield:私は1年ほど前にあなたの記事を読みました。ありがとう!少し追加をお勧めします。CLU言語の評価戦略を説明するためにBarbara Liskovによって発明された、この「値が参照である値による呼び出し」のこの形式を説明する特定の用語があることに言及してください1974、記事で取り上げられているような混乱を避けるために:共有による呼び出しオブジェクト共有による呼び出し、または単にオブジェクトによる呼び出しとも呼ばれます)。これは、セマンティクスをほぼ完全に記述しています。
イェルクWミッターク

29
@Gevorg-C / C ++言語は「ポインター」の概念を所有していません。ポインターを使用するが、C / C ++で許可されているのと同じタイプのポインター操作を許可しない他の言語があります。Javaにはポインタがあります。いたずらから保護されているだけです。
Scott Stanchfield、2011

1740

Javaは常に参照ではなく、値によって引数を渡します。


を通してこれを説明しましょう:

public class Main {

     public static void main(String[] args) {
          Foo f = new Foo("f");
          changeReference(f); // It won't change the reference!
          modifyReference(f); // It will modify the object that the reference variable "f" refers to!
     }

     public static void changeReference(Foo a) {
          Foo b = new Foo("b");
          a = b;
     }

     public static void modifyReference(Foo c) {
          c.setAttribute("c");
     }

}

これを段階的に説明します。

  1. fタイプの名前付き参照を宣言し、FooそれにFoo属性付きのタイプの新しいオブジェクトを割り当てます"f"

    Foo f = new Foo("f");

    ここに画像の説明を入力してください

  2. メソッド側から、Foo名前付きの型の参照aが宣言され、最初に割り当てられnullます。

    public static void changeReference(Foo a)

    ここに画像の説明を入力してください

  3. メソッドを呼び出すchangeReferenceと、参照にaは引数として渡されるオブジェクトが割り当てられます。

    changeReference(f);

    ここに画像の説明を入力してください

  4. bタイプの名前付き参照を宣言し、FooそれにFoo属性付きのタイプの新しいオブジェクトを割り当てます"b"

    Foo b = new Foo("b");

    ここに画像の説明を入力してください

  5. a = b参照に新しい割り当てを行いaない f、そのその属性であるオブジェクトの"b"

    ここに画像の説明を入力してください

  6. modifyReference(Foo c)メソッドを呼び出すと、参照cが作成され、属性を持つオブジェクトが割り当てられます"f"

    ここに画像の説明を入力してください

  7. c.setAttribute("c");参照しているオブジェクトの属性を変更します。参照cしているのは同じオブジェクトですf

    ここに画像の説明を入力してください

引数としてオブジェクトを渡す方法がJavaでどのように機能するかを理解していただければ幸いです。


82
+1いいもの。良い図。私はまた素敵な簡潔ページここで見つけるadp-gmbh.ch/php/pass_by_reference.html私はそれはPHPで書かれて認めるが、それは私が考えていることの違いを理解するpricipleでOKが重要である(とにその違いを操作する方法あなたの要望)。
DaveM 2013年

5
@ Eng.Fouadは素晴らしい説明ですがa、同じオブジェクトを指している場合f(およびオブジェクトの独自のコピーを決して取得していない場合f)を使用して行われたオブジェクトへのa変更fは、両方とも同じオブジェクトで機能しているため、同様に変更する必要があります)、そのため、ある時点で、オブジェクトポイントのa独自のコピーを取得する必要がありますf
0x6C38

14
@MrDが 'a'が同じオブジェクト 'f'を指す場合、 'a'を介してそのオブジェクトに加えられた変更は、 'f'を介しても監視できますが、 'f'は変更されませんでした。「f」はまだ同じオブジェクトを指しています。オブジェクトは完全に変更できますが、「f」が指すものを変更することはできません。これは、何らかの理由で一部の人が理解できない根本的な問題です。
Mike Braun、

@MikeBraun ...なに?今あなたは私を混乱させました:S。あなたが書いたものは、6。と7.が示すものとは反対のものではありませんか?
邪悪な洗濯機

6
これが私が見つけた最良の説明です。ところで、基本的な状況はどうですか?たとえば、引数にはint型が必要ですが、int変数のコピーを引数に渡しますか?
アレンワンガ2016年

732

これにより、参照渡しまたは値渡しのJavaに関する次のディスカッションでJavaが実際に機能する方法についての洞察が得られます。

ステップ1は、特に他のプログラミング言語から来ている場合は、「p」「_ _ _ _ _ _ _」で始まる単語を覚えておいてください。Javaと「p」を同じ本、フォーラム、またはtxtで書くことはできません。

ステップ2では、オブジェクトをメソッドに渡すときは、オブジェクト自体ではなく、オブジェクト参照を渡すことを覚えておいてください。

  • 学生:マスター、これはJavaが参照渡しであることを意味しますか?
  • マスター:バッタ、いいえ

オブジェクトの参照/変数が何をする/するかを考えてみましょう:

  1. 変数は、メモリ(ヒープ)内の参照されるオブジェクトに到達する方法をJVMに指示するビットを保持します。
  2. メソッドに引数を渡すときは、参照変数ではなく、参照変数のビットのコピーを渡します。このようなもの:3bad086a。3bad086aは、渡されたオブジェクトに到達する方法を表します。
  3. つまり、3bad086aを渡すだけで、それが参照の値になります。
  4. 参照自体ではなく(オブジェクトではなく)参照の値を渡している。
  5. この値は実際にはコピーされ、メソッドに渡されます。

以下では(これをコンパイル/実行しないでください...):

1. Person person;
2. person = new Person("Tom");
3. changeName(person);
4.
5. //I didn't use Person person below as an argument to be nice
6. static void changeName(Person anotherReferenceToTheSamePersonObject) {
7.     anotherReferenceToTheSamePersonObject.setName("Jerry");
8. }

何が起こるのですか?

  • 変数personは行#1で作成され、最初はnullです。
  • 新しいPersonオブジェクトが行#2で作成され、メモリに格納され、変数personにPersonオブジェクトへの参照が与えられます。つまり、そのアドレスです。3bad086aとしましょう。
  • オブジェクトのアドレスを保持する変数personは、3行目の関数に渡されます。
  • 4行目で静寂の音を聞くことができます
  • 5行目のコメントを確認してください
  • メソッドローカル変数-anotherReferenceToTheSamePersonObject-が作成され、マジックが6行目に追加されます。
    • 変数/参照はビットごとにコピーされ、関数内の別のReferenceToTheSamePersonObjectに渡されます。
    • Personの新しいインスタンスは作成されません。
    • person」と「anotherReferenceToTheSamePersonObject」はどちらも同じ3bad086aの値を保持しています。
    • これを試してはいけませんが、person == anotherReferenceToTheSamePersonObjectはtrueになります。
    • 両方の変数は、参照のIDOPICAL COPIESを持ち、どちらも同じPersonオブジェクト、ヒープ上のSAMEオブジェクトであり、COPYではありません。

写真は千の言葉に値します:

値渡し

anotherReferenceToTheSamePersonObject矢印は、変数personではなく、Objectに向けられていることに注意してください。

うまくいかなかった場合は、私を信頼して、Javaは値渡しであると言ったほうがよいことを覚えておいてください。さて、参照値渡します。まあ、変数値のコピーによる受け渡しはさらに優れています!;)

ここで私を嫌いにしてください。ただし、メソッドの引数について話すとき、プリミティブデータ型とオブジェクトを渡すことには違いがないことに注意してください。

あなたは常にリファレンスの値のビットのコピーを渡します!

  • プリミティブデータタイプの場合、これらのビットにはプリミティブデータタイプ自体の値が含まれます。
  • オブジェクトの場合、ビットには、JVMにオブジェクトへのアクセス方法を指示するアドレスの値が含まれます。

メソッド内では参照オブジェクトを好きなだけ変更できるのでJavaは値渡しですが、どんなに頑張っても、参照し続ける渡された変数を変更することはできません(p _ _ _ではなく) _ _ _ _)何があっても同じオブジェクト!


上記のchangeName関数は、渡された参照の実際のコンテンツ(ビット値)を変更することはできません。つまり、changeNameはPerson personに別のObjectを参照させることはできません。


もちろん、短くして、Javaは値渡しであると言え ます。


5
ポインタを意味しますか?..正しく取得できた場合public void foo(Car car){ ... }carでローカルにfooあり、オブジェクトのヒープ位置が含まれていますか?carの値をで変更car = new Car()すると、ヒープ上の別のオブジェクトを指しますか?carのプロパティ値をで変更するcar.Color = "Red"と、で示されるヒープ内のオブジェクトcarが変更されます。また、C#でも同じですか?返信してください!ありがとう!
dpp

11
@domanokzあなたは私を殺している、その言葉を二度と言わないでください!;)「リファレンス」も言わなくても、この質問に答えることができたはずです。これは用語の問題であり、「p」を指定すると悪化します。私とスコットは、残念ながらこれについて異なる見解を持っています。私はそれがJavaでどのように機能するかを理解したと思います。これで、値渡し、オブジェクト共有、変数値のコピー渡し、または他の方法を自由に考えて呼び出すことができます。あなたがそれがどのように機能し、Object型の変数に何があるかを理解している限り、私は本当に気にしません:ちょうど私書箱アドレス!;)
Marsellus Wallace

9
あなたはちょうどリファレンスを渡したように聞こえますか?私はJavaがまだコピーされた参照渡し言語であるという事実を支持します。それがコピーされた参照であるという事実は、用語を変更しません。どちらのREFERENCESも同じオブジェクトを指しています。これは純粋主義者の主張です...
ジョン・ストリクラー2014年

3
では、理論的なライン#9 System.out.println(person.getName());に立ち寄ってください。何が表示されますか?「トム」または「ジェリー」?これは私がこの混乱を防ぐのに役立つ最後のものです。
TheBrenny 2014年

1
リファレンスの価値」は、それを最終的に私に説明したものです。
アレクサンダーシューベルト

689

Javaは常に、例外なく、値渡しされ、これまで

それで、誰もがこれによってまったく混乱する可能性があり、Javaが参照渡しであると信じるのか、または参照渡しとして機能するJavaの例があると思いますか?重要な点は、どのような状況において、Java がオブジェクト自体の値に直接アクセスすること決してないということです。オブジェクトへの唯一のアクセスは、そのオブジェクトへの参照を介したものです。Javaオブジェクトは直接ではなく常に参照を通じてアクセスされるため、フィールドや変数、メソッドの引数オブジェクトであると説明することはよくあります混乱は、命名法のこの(厳密に言えば、正しくない)変更から生じます。

したがって、メソッドを呼び出すとき

  • プリミティブ引数(intlongなど)の場合、値渡しはプリミティブの実際の値です(たとえば、3)。
  • オブジェクトの場合、値渡しはオブジェクトへの参照の値です。

だから、あなたが持っている場合doSomething(foo)public void doSomething(Foo foo) { .. }2つのFOOSをコピーした参照を、同じオブジェクトをポイントします。

当然、オブジェクトへの参照を値で渡すことは、参照でオブジェクトを渡すことと非常によく似ています(実際には区別できません)。


7
プリミティブの値は(Stringのように)不変であるため、2つのケースの違いは実際には関係ありません。
–PaŭloEbermann、2011

4
丁度。監視可能なJVMの動作でわかるすべてのことについて、プリミティブは参照によって渡され、ヒープ上に存在する可能性があります。そうではありませんが、実際には観測できません。
重力、

6
プリミティブは不変ですか?これはJava 7の新機能ですか?
user85421 2011

4
ポインタは不変で、プリミティブは一般に変更可能です。文字列もプリミティブではなく、オブジェクトです。さらに、文字列の基礎となる構造は可変配列です。唯一不変なのは長さです。これは配列固有の性質です。
kingfrito_5005

3
これは、議論の主に意味論的な性質を指摘する別の答えです。この回答で提供される参照の定義により、Javaは「参照渡し」になります。著者は基本的に、「参照渡し」とは「実際には区別できない」と述べて、最後の段落でも同じように認めています。OPがJavaの実装を理解したいのではなく、Javaを正しく使用する方法を理解したいという欲求のために尋ねているのではないかと思います。それが実際に区別がつかなければ、気にしても意味がなく、それについて考えることも時間の無駄です。
Loduwijk 2017

331

Javaは参照を値で渡します。

したがって、渡される参照を変更することはできません。


27
しかし、「引数で渡されたオブジェクトの値を変更することはできない」という繰り返しが繰り返されることは明らかに誤りです。別のオブジェクトを参照させることはできないかもしれませんが、それらのメソッドを呼び出すことによってそれらの内容を変更できます。IMOこれは、参照のすべての利点を失うことを意味し、追加の保証はありません。
Timmmm、

34
「引数で渡されたオブジェクトの値を変更することはできません」と言ったことはありません。「メソッド引数として渡されたオブジェクト参照の値を変更することはできません」と言います。これは、Java言語についての真の声明です。もちろん、オブジェクトの状態は変更できます(不変でない限り)。
ScArcher2 2011

20
Javaでオブジェクトを実際に渡すことはできないことに注意してください。オブジェクトはヒープ上に残ります。オブジェクトへのポインタを渡すことができます(呼び出されたメソッドのスタックフレームにコピーされます)。したがって、渡された値(ポインタ)を変更することはありませんが、それに従って自由に値を指定し、ヒープが指す値を変更することができます。それは値渡しです。
スコットスタンフィールド

10
あなたはちょうどリファレンスを渡したように聞こえますか?私はJavaがまだコピーされた参照渡し言語であるという事実を支持します。それがコピーされた参照であるという事実は、用語を変更しません。どちらのREFERENCESも同じオブジェクトを指しています。これは純粋主義者の主張です...
ジョン・ストリクラー2014年

3
Javaはオブジェクトを渡さず、ポインタの値をオブジェクトに渡します。これにより、元のオブジェクトのそのメモリ位置への新しいポインタが新しい変数に作成されます。メソッドでこのポインター変数の値(ポインターが指すメモリーアドレス)を変更した場合、メソッドの呼び出し元で使用される元のポインターは変更されません。パラメータを参照と呼ぶ場合、元の参照自体ではなく元の参照のコピーであるため、オブジェクトへの参照が2つになり、値渡しであることを意味します
theferrit32

238

「参照渡しvs値渡し」について議論するのはあまり役に立ちません。

「Javaは何でも渡す(参照/値)」と言った場合、どちらの場合も完全な答えは得られません。記憶で何が起こっているのかを理解するのに役立つと思われる追加情報を以下に示します。

Java実装に到達する前に、スタック/ヒープのコースをクラッシュします。値は、カフェテリアでのプレートのスタックのように、整然とした方法でスタックに出入りします。ヒープ内のメモリ(動的メモリとも呼ばれます)は無計画で無秩序です。JVMはできる限りスペースを見つけ、それを使用する変数が不要になったときに解放します。

はい。まず、ローカルプリミティブがスタックに入ります。したがって、このコード:

int x = 3;
float y = 101.1f;
boolean amIAwesome = true;

この結果:

スタック上のプリミティブ

オブジェクトを宣言してインスタンス化するとき。実際のオブジェクトはヒープに置かれます。スタックには何が入っていますか?ヒープ上のオブジェクトのアドレス。C ++プログラマーはこれをポインターと呼びますが、一部のJava開発者は「ポインター」という単語に反対しています。なんでも。オブジェクトのアドレスがスタックに入れられることを知ってください。

そのようです:

int problems = 99;
String name = "Jay-Z";

ab * 7ch aint one!

配列はオブジェクトなので、ヒープにも適用されます。そして、配列内のオブジェクトはどうですか?それらは独自のヒープスペースを取得し、各オブジェクトのアドレスは配列内に入ります。

JButton[] marxBros = new JButton[3];
marxBros[0] = new JButton("Groucho");
marxBros[1] = new JButton("Zeppo");
marxBros[2] = new JButton("Harpo");

マルクス兄弟

では、メソッドを呼び出すと何が渡されますか?オブジェクトを渡す場合、実際に渡すのはオブジェクトのアドレスです。アドレスの「値」と言う人もいれば、オブジェクトへの参照にすぎないと言う人もいます。これは、「参照」と「価値」の支持者の間の聖戦の起源です。何が渡されるかはオブジェクトのアドレスであることを理解するほど重要ではありません。

private static void shout(String name){
    System.out.println("There goes " + name + "!");
}

public static void main(String[] args){
    String hisName = "John J. Jingleheimerschmitz";
    String myName = hisName;
    shout(myName);
}

1つの文字列が作成され、そのスペースがヒープに割り当てられますhisName。2番目の文字列のアドレスは最初の文字列と同じであるため、文字列へのアドレスはスタックに格納され、識別子が与えられます。新しい文字列は作成されず、新しいヒープ領域は割り当てられませんが、新しい識別子がスタックに作成されます。次に呼び出しますshout()。新しいスタックフレームが作成され、新しい識別子nameが作成されて、既存の文字列のアドレスが割り当てられます。

ラダディダダダダ

それで、値、参照?あなたは「ジャガイモ」と言います。


7
ただし、関数が、参照先のアドレスを持つ変数を変更するように見える、より複雑な例をフォローアップする必要があります。
ブライアンピーターソン

34
それだから人々は、ヒープ対スタックの「本当の問題の周りの踊り」ではありませんではない本当の問題。それはせいぜい実装の詳細であり、最悪の場合はまったく間違っています。(「エスケープ解析を」Googleとオブジェクトの膨大な数は、おそらくプリミティブ含まれるオブジェクトがスタック上で生活することは十分に可能だ。ない本当の問題は、スタック上のライブを。)正確に参照型と値型の違い-特に、参照型変数の値は参照であり、それが参照するオブジェクトではありません。
cHao

8
これは「実装の詳細」であり、オブジェクトがメモリ内のどこにあるかをJavaが実際に示す必要はなく、実際にはその情報の漏えいを避けることが決定されているようです。それはオブジェクトをスタックに置く可能性があり、あなたは決して知りません。気にするなら、あなたは間違ったことに焦点を合わせている-そしてこの場合、それは本当の問題を無視することを意味する。
cHao

10
どちらにしても、「プリミティブがスタックに入る」のは誤りです。プリミティブローカル変数はスタックに置かれます。(もちろん、それらが最適化されていない場合)。しかし、ローカル参照変数も同様です。また、オブジェクト内に定義されたプリミティブメンバーは、オブジェクトが存在する場所に存在します。
cHao

9
ここのコメントに同意してください。スタック/ヒープは副次的な問題であり、関連性はありません。一部の変数はスタック上にあり、一部は静的メモリ(静的変数)にあり、ヒープ上に十分に存在します(すべてのオブジェクトメンバー変数)。これらの変数はいずれも参照渡しできません。呼び出されたメソッドから、引数として渡された変数の値を変更することはできません。したがって、Javaには参照渡しはありません。
魚釣り2014年

195

対比を示すために、次のC ++Javaのスニペットを比較します。

C ++の場合:注:不正なコード-メモリリーク! しかし、それは要点を示しています。

void cppMethod(int val, int &ref, Dog obj, Dog &objRef, Dog *objPtr, Dog *&objPtrRef)
{
    val = 7; // Modifies the copy
    ref = 7; // Modifies the original variable
    obj.SetName("obj"); // Modifies the copy of Dog passed
    objRef.SetName("objRef"); // Modifies the original Dog passed
    objPtr->SetName("objPtr"); // Modifies the original Dog pointed to 
                               // by the copy of the pointer passed.
    objPtr = new Dog("newObjPtr");  // Modifies the copy of the pointer, 
                                   // leaving the original object alone.
    objPtrRef->SetName("objRefPtr"); // Modifies the original Dog pointed to 
                                    // by the original pointer passed. 
    objPtrRef = new Dog("newObjPtrRef"); // Modifies the original pointer passed
}

int main()
{
    int a = 0;
    int b = 0;
    Dog d0 = Dog("d0");
    Dog d1 = Dog("d1");
    Dog *d2 = new Dog("d2");
    Dog *d3 = new Dog("d3");
    cppMethod(a, b, d0, d1, d2, d3);
    // a is still set to 0
    // b is now set to 7
    // d0 still have name "d0"
    // d1 now has name "objRef"
    // d2 now has name "objPtr"
    // d3 now has name "newObjPtrRef"
}

Javaでは、

public static void javaMethod(int val, Dog objPtr)
{
   val = 7; // Modifies the copy
   objPtr.SetName("objPtr") // Modifies the original Dog pointed to 
                            // by the copy of the pointer passed.
   objPtr = new Dog("newObjPtr");  // Modifies the copy of the pointer, 
                                  // leaving the original object alone.
}

public static void main()
{
    int a = 0;
    Dog d0 = new Dog("d0");
    javaMethod(a, d0);
    // a is still set to 0
    // d0 now has name "objPtr"
}

Javaには、2つのタイプの受け渡しのみがあります。組み込みタイプの値によるものと、オブジェクト型のポインターの値によるものです。


7
+1 Dog **objPtrPtrC ++の例にも追加します。これにより、ポインターが「指す」ものを変更できます。
Amro、2014

1
しかし、これはJavaでの与えられた質問には答えません。実際問題として、JavaのメソッドのすべてがPass By Valueです。
tauitdnmd 2018年

2
この例は、JavaがCでポインタと同等のポインタを使用していることを証明しているだけですか?Cの値渡しは基本的に読み取り専用ですか?最後に、このスレッド全体は理解できません
amdev '21 / 02/21


166

基本的に、オブジェクトパラメータを再割り当てしても引数には影響しません。たとえば、

private void foo(Object bar) {
    bar = null;
}

public static void main(String[] args) {
    String baz = "Hah!";
    foo(baz);
    System.out.println(baz);
}

"Hah!"代わりに印刷されますnull。これが機能する理由は、barbazへの参照であるの値のコピーであるためです"Hah!"。それ自体が実際の参照である場合は、にfoo再定義さbaznullます。


8
むしろ、barは最初に同じオブジェクトを指す参照baz(またはbazエイリアス)のコピーであると言いたいです。
MaxZoom、2015

Stringクラスと他のすべてのクラスの間にはわずかな違いはありませんか?
Mehdi Karamosly

152

まだ誰もバーバラ・リスコフについて言及していないとは信じられません。彼女は1974年にCLUを設計したとき、彼女はこの同じ用語の問題に走った、と彼女は長期考案共有によってコール(としても知られているオブジェクトを共有することにより通話オブジェクトによって呼び出しを値がある値による呼び出し」のこの特定のケースのために)参照」。


3
私はこの命名法の区別が好きです。残念なことに、Javaはオブジェクトの共有による呼び出しをサポートしていますが、値による呼び出しはサポートしていません(C ++はサポートしています)。Javaは、プリミティブデータ型に対してのみ値による呼び出しをサポートし、複合データ型に対してはサポートしません。
Derek Mahar

2
余分な用語が必要だとは本当に思っていません。これは、特定のタイプの値の値渡しにすぎません。「プリミティブによる呼び出し」を追加すると、説明が追加されますか?
Scott Stanchfield、


したがって、いくつかのグローバルコンテキストオブジェクトを共有したり、他の参照を含むコンテキストオブジェクトを渡したりして、参照渡しを行うことができますか?私はまだ値渡しをしていますが、少なくとも私は参照にアクセスでき、変更して他のものを指すようにすることができます。
YoYo

1
@Sanjeev:オブジェクト共有による呼び出しは、値渡しによる特殊なケースです。ただし、Java(およびPython、Ruby、ECMAScript、Smalltalkなどの同様の言語)は参照渡しであると多くの人が激しく主張しています。私もそれを値渡しと呼びたいと思いますが、オブジェクト渡しは、値渡しではないと主張する人でも同意できる合理的な用語のようです。
イェルクWミッターク

119

問題の核心は、ワードということである参照表現では、単語の通常の意味とは全く異なる手段何か「参照渡し」参照のJavaインチ

通常、Java 参照で、オブジェクトへの参照を意味します。しかし、専門用語はプログラミング言語の理論からの参照/値によって渡され、変数を保持するメモリセルへの参照について話します。これは、まったく異なるものです。


7
@Gevorg-次に、「NullPointerException」とは何ですか?
ホットリックス

5
@Hot:Javaが明確な用語に落ち着く前の、残念ながら名前が付けられた例外。c#で意味的に同等の例外はNullReferenceExceptionと呼ばれます。
JacquesB 2013

4
Java用語での「参照」の使用は、理解を妨げる影響であるように私には常に思われました。
Hot Licks 2013

これらのいわゆる「オブジェクトへのポインタ」を「オブジェクトへのハンドル」と呼ぶことを学びました。これにより、あいまいさが減少しました。
moronkreacionz

86

Javaではすべてが参照であるため、Point pnt1 = new Point(0,0);次のようなものがある場合: Javaは次のことを行います:

  1. 新しいPointオブジェクトを作成します
  2. 新しいPoint参照を作成し、以前に作成されたPointオブジェクトのポイント(参照)への参照を初期化します。
  3. ここから、Pointオブジェクトライフを通じて、pnt1参照を通じてそのオブジェクトにアクセスします。したがって、Javaでは、その参照を介してオブジェクトを操作すると言えます。

ここに画像の説明を入力してください

Javaは参照によってメソッド引数を渡しません。値で渡します。このサイトの例を使用します

public static void tricky(Point arg1, Point arg2) {
  arg1.x = 100;
  arg1.y = 100;
  Point temp = arg1;
  arg1 = arg2;
  arg2 = temp;
}
public static void main(String [] args) {
  Point pnt1 = new Point(0,0);
  Point pnt2 = new Point(0,0);
  System.out.println("X1: " + pnt1.x + " Y1: " +pnt1.y); 
  System.out.println("X2: " + pnt2.x + " Y2: " +pnt2.y);
  System.out.println(" ");
  tricky(pnt1,pnt2);
  System.out.println("X1: " + pnt1.x + " Y1:" + pnt1.y); 
  System.out.println("X2: " + pnt2.x + " Y2: " +pnt2.y);  
}

プログラムの流れ:

Point pnt1 = new Point(0,0);
Point pnt2 = new Point(0,0);

2つの異なる参照が関連付けられた2つの異なるPointオブジェクトを作成します。 ここに画像の説明を入力してください

System.out.println("X1: " + pnt1.x + " Y1: " +pnt1.y); 
System.out.println("X2: " + pnt2.x + " Y2: " +pnt2.y);
System.out.println(" ");

期待どおりの出力は次のようになります。

X1: 0     Y1: 0
X2: 0     Y2: 0

この行では、「値渡し」が登場します...

tricky(pnt1,pnt2);           public void tricky(Point arg1, Point arg2);

参考文献pnt1pnt2されている値渡しトリッキーな方法を、今、あなたの参照手段pnt1とは、pnt2自分の持っているcopiesという名前arg1arg2。だからpnt1arg1 ポイントを同じオブジェクトに。(pnt2およびと同じarg2ここに画像の説明を入力してください

ではtricky方法:

 arg1.x = 100;
 arg1.y = 100;

ここに画像の説明を入力してください

trickyメソッドの次

Point temp = arg1;
arg1 = arg2;
arg2 = temp;

ここでは、最初に、参照のように同じ場所temp指す新しいポイント参照を作成しarg1ます。次に、参照のように同じ場所arg1指すように参照を移動しarg2ます。最後にのような同じ場所arg2指しtempます。

ここに画像の説明を入力してください

ここからの範囲tricky方法がなくなって、あなたは、それ以上の言及にはアクセスできません:arg1arg2tempしかし、重要な注意点は、彼らが「生活の中で」されているときは、これらの参照を行うことすべてが永久に彼らがされているオブジェクトに影響しますある地点までを。

したがって、methodを実行した後tricky、に戻るとmain、次のような状況になります。 ここに画像の説明を入力してください

したがって、プログラムの完全な実行は次のようになります。

X1: 0         Y1: 0
X2: 0         Y2: 0
X1: 100       Y1: 100
X2: 0         Y2: 0

7
Javaで「Javaですべてが参照である」と言う場合、すべてのオブジェクトが参照で渡されることを意味しますプリミティブデータ型は参照渡しされません。
Eric

スワップされた値をメインメソッドで出力できます。トリッキーな方法で、次のステートメントを追加しますarg1.x = 1; arg1.y = 1; arg2.x = 2; arg2.y = 2;。arg1がpnt2参照を保持し、arg2が現在pnt1参照を保持しているため、その印刷X1: 2 Y1: 2 X2: 1 Y2: 1
Shahid Ghafoor

85

Javaは常に参照渡しではなく値渡しです

まず、値渡しと参照渡しが何であるかを理解する必要があります。

値渡しとは、渡される実際のパラメーターの値のコピーをメモリー内に作成することを意味します。これは、実際のパラメーターの内容のコピーです

参照渡し(アドレス渡しとも呼ばれる)は、実際のパラメーターのアドレスのコピーが保管されることを意味します

時々Javaは参照渡しの錯覚を与えることができます。以下の例を使用して、それがどのように機能するかを見てみましょう。

public class PassByValue {
    public static void main(String[] args) {
        Test t = new Test();
        t.name = "initialvalue";
        new PassByValue().changeValue(t);
        System.out.println(t.name);
    }

    public void changeValue(Test f) {
        f.name = "changevalue";
    }
}

class Test {
    String name;
}

このプログラムの出力は次のとおりです。

changevalue

ステップバイステップで理解しましょう:

Test t = new Test();

ご存知のように、ヒープ内にオブジェクトを作成し、参照値をtに返します。たとえば、tの値が0x100234(実際のJVM内部値がわからない、これは単なる例である)とします。

最初のイラスト

new PassByValue().changeValue(t);

参照tを関数に渡す場合、オブジェクトテストの実際の参照値を直接渡しませんが、tのコピーを作成してから関数に渡します。値渡しなので、実際の変数の参照ではなく、変数のコピーを渡します。tの値はであると述べたので0x100234、tとfは同じ値を持ち、したがって同じオブジェクトを指します。

2番目の図

リファレンスfを使用して関数内の何かを変更すると、オブジェクトの既存の内容が変更されます。そのためchangevalue、関数で更新された出力を取得しました。

これをより明確に理解するために、次の例を検討してください。

public class PassByValue {
    public static void main(String[] args) {
        Test t = new Test();
        t.name = "initialvalue";
        new PassByValue().changeRefence(t);
        System.out.println(t.name);
    }

    public void changeRefence(Test f) {
        f = null;
    }
}

class Test {
    String name;
}

これはスローしNullPointerExceptionますか?いいえ、参照のコピーのみを渡すためです。参照渡しの場合、NullPointerException以下のようにをスローする可能性があります。

3番目の図

うまくいけば、これが役立ちます。


71

参照は、使用する言語に関係なく、表現されるときは常に値です。

ボックスビューの外に出て、アセンブリまたはいくつかの低レベルのメモリ管理を見てみましょう。CPUレベルでは、メモリまたはCPUレジスタの1つに書き込まれると、何かへの参照はすぐにになります。(それがポインタが適切な定義である理由です。それは同時に目的を持つ値です)。

メモリ内のデータには場所があり、その場所には値(バイト、ワードなど)があります。アセンブリには、特定の場所(変数)に名前を付ける便利なソリューションがありますが、コードをコンパイルするとき、ブラウザがドメイン名をIPアドレスに置き換えるのと同じように、アセンブラは名前を指定された場所に置き換えるだけです。

コアに至るまで、それを表すことなく任意の言語で何かへの参照を渡すことは(それがすぐに値になるとき)技術的に不可能です。

変数Fooがあり、その場所がメモリの47番目のバイトにあり、そのが5であるとします。別の変数Ref2Fooがメモリの223番目のバイトにあり、その値は47になります。このRef2Fooは技術変数かもしれません、プログラムによって明示的に作成されたものではありません。他の情報なしで5と47を見ると、2つのだけが表示されます。それらを参照として使用する場合、到達する5ために旅行する必要があります。

(Name)[Location] -> [Value at the Location]
---------------------
(Ref2Foo)[223]  -> 47
(Foo)[47]       -> 5

これがジャンプテーブルのしくみです。

Fooの値を使用してメソッド/関数/プロシージャを呼び出す場合、言語とそのいくつかのメソッド呼び出しモードに応じて、変数をメソッドに渡す方法がいくつかあります。

  1. 5は、CPUレジスタの1つ(つまり、EAX)にコピーされます。
  2. 5はスタックにPUSHdします。
  3. 47がいずれかのCPUレジスタにコピーされる
  4. 47スタックにプッシュします。
  5. 223がいずれかのCPUレジスタにコピーされます。
  6. 223はスタックにPUSHdを取得します。

いずれの場合も、値- 既存の値のコピー -が作成され、それを処理するのは受信側のメソッドです。メソッド内で "Foo"を書き込むと、それはEAXから読み取られるか、自動的に 逆参照されるか、または二重逆参照されます。プロセスは、言語の動作方法やFooの型によって決まります。これは、逆参照プロセスを回避するまで、開発者からは隠されています。そう基準は、あるの基準は、(言語レベルで)処理されなければならない値であるため、表現します、。

これで、Fooをメソッドに渡しました。

  • ケース1.と2.の場合。Foo(Foo = 9)を変更すると、値のコピーがあるため、ローカルスコープにのみ影響します。メソッドの内部からは、元のFooがメモリ内のどこにあったのかさえ判断できません。
  • ケース3.と4.の場合。デフォルトの言語構成を使用してFoo(Foo = 11)を変更すると、Fooがグローバルに変更される可能性があります(言語に依存します。つまり、JavaまたはPascalのprocedure findMin(x, y, z: integer;var mなど: integer);)。言語はあなたが間接参照プロセスを回避することを可能にする場合は、あなたが変更することができ47、と言います49。その時点でFooを読んだ場合、Fooはローカルポインターを変更しているため、変更されているようです。また、メソッド(Foo = 12)内でこのFooを変更する場合は、プログラム(別名segfault)の実行がFUBARになるため、予想とは異なるメモリに書き込むため、実行可能ファイルを保持する予定の領域を変更することもできます。プログラムとそれに書き込むと、実行中のコードが変更されます(Fooはにありません47)。しかし、フーの値4747メソッドへのコピーでもあるため、メソッド内の1つだけで、グローバルに変更されませんでした。
  • ケース5と6.の場合223、メソッド内で変更すると、3または4と同じ騒乱が発生します(現在は不良値を指しているポインターが、ポインターとして再び使用されます)が、これはまだローカルです。 223がコピーされたため、問題。ただし、参照先の値Ref2Foo(つまり223)に到達し、ポイントされた値47、たとえばに変更できる場合、49Fooはグローバルに影響します。この場合、メソッドはコピーを取得します223 が、参照先47が存在するのは1回だけであり、 to 49は、すべてのRef2Foo二重逆参照を間違った値に導きます。

取るに足らない詳細を手間をかけて、参照渡しを行う言語でさえ値を関数に渡しますが、それらの関数は逆参照の目的で値を使用する必要があることを知っています。この参照としての値渡しは、実際には役に立たず、用語は参照渡しのみであるため、プログラマーから単に隠されています。

厳密な値渡しも無意味です。つまり、配列を引数としてメソッドを呼び出すたびに100Mバイトの配列をコピーする必要があるため、Javaを厳密に値渡しすることはできません。すべての言語はこの巨大な配列への参照を(値として)渡し、その配列をメソッド内でローカルに変更できる場合はコピーオンライトメカニズムを採用するか、(Javaが行うように)メソッドが配列をグローバルに(呼び出し元のビュー)といくつかの言語では、参照自体の値を変更できます。

つまり、簡単に言うと、Java自体の用語では、Javaは値渡しですは、実際の値または参照を表す値のいずれかです。


1
参照渡しの言語では、渡されるもの(参照)は一時的なものです。受信者はそれをコピーすることを「想定」されていません。Javaでは、配列の受け渡しは「オブジェクト識別子」です。つまり、構築された24601番目のオブジェクトが配列であった場合、「オブジェクト#24601」と書かれた紙片に相当します。受信者は「オブジェクト#24601」を好きな場所にコピーでき、「オブジェクト#24601」と書かれた紙片を持っていれば誰でも任意の配列要素で好きなことができます。渡されたビットのパターンは、実際には「オブジェクト#24601」とは言いませんが、もちろん...
supercat

...重要な点は、配列を識別するオブジェクトIDの受信者は、そのオブジェクトIDをどこにでも保存して、誰にでも与えることができ、受信者はいつでも配列にアクセスしたり変更したりできることです。欲求。対照的に、そのようなことをサポートするPascalのような言語で参照によって配列が渡された場合、呼び出されたメソッドは配列に対して必要なことは何でも実行できますが、コードが配列を変更できるように参照を保存できませんでしたそれが戻った後。
スーパーキャット2014年

実際、Pascalでは、参照渡しまたはローカルコピーのいずれでも、すべての変数のアドレスを取得できます。addr型なし、型付き@、および参照された変数を後で変更できます(ローカルにコピーされた変数を除く)。しかし、あなたがそうする理由はわかりません。あなたの例のその紙片(Object#24601)は参照であり、その目的はメモリ内の配列を見つけるのを助けることであり、それ自体には配列データが含まれていません。プログラムを再起動すると、その内容が前回の実行と同じであっても、同じ配列が異なるオブジェクトIDを取得する場合があります。
空手犬2014年

"@"演算子は標準のPascalの一部ではなく、一般的な拡張機能として実装されていると思いました。それは規格の一部ですか?私のポイントは、真の参照渡しで、エフェメラルオブジェクトへの非エフェメラルポインタを構築する機能がない言語では、配列を渡す前に、ユニバース内の任意の場所に配列への唯一の参照を保持するコードでした。リファレンスは、受信者が「チート」しない限り、後で唯一のリファレンスを保持することを知ることができます。Javaでそれを達成する唯一の安全な方法は、一時オブジェクトを作成することです...
スーパーキャット2014年

...これはをカプセル化しAtomicReference、参照もそのターゲットも公開しませんが、代わりにターゲットに対して処理を行うメソッドを含みます。オブジェクトが渡されたコードが返されたら、AtomicReference[作成者が直接参照を保持していた]を無効にして破棄する必要があります。それは適切なセマンティクスを提供しますが、遅くて厄介です。
スーパーキャット2014年

70

Javaは値による呼び出しです

使い方

  • あなたは常にリファレンスの値のビットのコピーを渡します!

  • プリミティブデータタイプの場合、これらのビットにはプリミティブデータタイプ自体の値が含まれます。そのため、メソッド内のヘッダーの値を変更しても、外部の変更は反映されません。

  • それはのようなオブジェクト・データ型だ場合はFoo fooという=新しいはFoo()このケースではオブジェクトのアドレスのコピーは、ファイルのショートカットのように渡し、その後、我々はテキストファイルがあるとしabc.txt\デスクトップ:Cを、我々はのショートカットを作ると仮定同じファイルをC:\ desktop \ abc-shortcut内に配置して、C:\ desktop \ abc.txtからファイルにアクセスし、「Stack Overflow」を書き込んでファイルを閉じ、もう一度ショートカットからファイルを開くと、次のようになります。「プログラマーが学習する最大のオンラインコミュニティーである」と書くと、ファイルの変更の合計は「スタックオーバーフローがプログラマーが学習する最大のオンラインコミュニティー」になりますつまり、同じファイルにアクセスするたびに、ファイルをどこから開いてもかまいません。ここでは、Fooをファイルとして想定し、fooを123hd7hC:\ desktop \ abc.txtのような元のアドレス)に格納するとします。アドレスと234jdidC:\ desktop \ abc-shortcutのようにコピーされたアドレスで、実際にはファイルの元のアドレスが含まれています)..理解を深めるために、ショートカットファイルを作成して感じてください。


66

これをカバーする素晴らしい答えがすでにあります。C ++での参照渡しとJavaでの値渡しの間の動作を対比する非常に単純な例(コンパイルされる)を共有することで、小さな貢献をしたいと思いました。

いくつかのポイント:

  1. 「参照」という用語は、2つの異なる意味を持つ多重定義です。Javaでは単にポインタを意味しますが、「参照渡し」のコンテキストでは、渡された元の変数へのハンドルを意味します。
  2. Javaは値渡しです。Javaは(他の言語の中でも)Cの子孫です。Cより前は、FORTRANやCOBOLなどのいくつかの(すべてではない)初期の言語がPBRをサポートしていましたが、Cはサポートしていませんでした。PBRにより、これらの他の言語は、サブルーチン内で渡された変数に変更を加えることができました。同じことを行うため(つまり、関数内の変数の値を変更するため)、Cプログラマーは変数へのポインターを関数に渡しました。JavaなどのCに触発された言語は、このアイデアを取り入れ、Javaがそのポインターの参照を呼び出すことを除いて、Cと同じようにポインターをメソッドに渡し続けます。繰り返しになりますが、これは「参照による受け渡し」とは異なる「参照」という言葉の使い方です。
  3. C ++では、「&」文字(CとC ++の両方で「変数のアドレス」を示すために使用される文字と同じ文字)を使用して参照パラメーターを宣言することにより、参照渡しを許可しています。たとえば、ポインターを参照渡しする場合、パラメーターと引数は同じオブジェクトを指しているだけではありません。むしろ、それらは同じ変数です。一方が別のアドレスまたはnullに設定された場合、もう一方も設定されます。
  4. 以下のC ++の例では、nullで終了する文字列へのポインタ参照渡ししています。そして、以下のJavaの例では、文字列へのJava参照(これも、文字列へのポインタと同じ)を値で渡しています。コメントの出力に注意してください。

C ++の参照渡しの例:

using namespace std;
#include <iostream>

void change (char *&str){   // the '&' makes this a reference parameter
    str = NULL;
}

int main()
{
    char *str = "not Null";
    change(str);
    cout<<"str is " << str;      // ==>str is <null>
}

Javaは「Javaリファレンス」を値の例で渡します

public class ValueDemo{

    public void change (String str){
        str = null;
    }

     public static void main(String []args){
        ValueDemo vd = new ValueDemo();
        String str = "not null";
        vd.change(str);
        System.out.println("str is " + str);    // ==> str is not null!!
                                                // Note that if "str" was
                                                // passed-by-reference, it
                                                // WOULD BE NULL after the
                                                // call to change().
     }
}

編集する

何人かの人々がコメントを書いていて、私の例を見ていないか、c ++の例を理解していないようです。接続がどこにあるかはわかりませんが、C ++の例を推測することは明確ではありません。パスカルでは参照渡しがきれいに見えるので、同じ例をパスカルで投稿していますが、間違っている可能性があります。私は人々をもっと混乱させているだけかもしれません。望みません。

Pascalでは、参照渡しのパラメーターは「varパラメーター」と呼ばれます。以下の手順setToNilでは、パラメータ「ptr」の前にあるキーワード「var」に注意してください。ポインタがこのプロシージャに渡されると、参照によって渡されます。動作に注意してください。このプロシージャがptrをnilに設定すると(つまり、pascalはNULLを意味します)、引数をnilに設定します。Javaではそれを実行できません。

program passByRefDemo;
type 
   iptr = ^integer;
var
   ptr: iptr;

   procedure setToNil(var ptr : iptr);
   begin
       ptr := nil;
   end;

begin
   new(ptr);
   ptr^ := 10;
   setToNil(ptr);
   if (ptr = nil) then
       writeln('ptr seems to be nil');     { ptr should be nil, so this line will run. }
end.

編集2

Ken Arnold、James Gosling(Javaを発明した人)、およびDavid Holmesによる「THE Javaプログラミング言語」、第2章、セクション2.6.5 からの抜粋

メソッドへのすべてのパラメーターは「値渡し」で渡されます。つまり、メソッドのパラメータ変数の値は、引数として指定された呼び出し元のコピーです。

彼はオブジェクトについても同じことを続けています。。。

パラメータがオブジェクト参照である場合、「値によって」渡されるのはオブジェクト参照ではなく、オブジェクト参照であることに注意してください。

そして同じセクションの終わり近くで、彼はjavaが値によってのみ渡され、参照によって渡されないことについてより広い声明を出します。

Javaプログラミング言語はオブジェクトを参照渡ししません。 オブジェクト参照を値渡します。同じ参照の2つのコピーは同じ実際のオブジェクトを参照するため、1つの参照変数を介して行われた変更は、他の参照変数を介して表示されます。正確に1つのパラメーター受け渡しモード(渡し)があり、これにより物事がシンプルに保たれます

本のこのセクションでは、Javaでのパラメーターの受け渡し、および参照渡しと値渡しの違いについて、Javaの作成者が詳しく説明しています。特に確信が持てない場合は、どなたでもお読みになることをお勧めします。

2つのモデルの違いは非常に微妙だと思います。実際に参照渡しを使用したプログラミングを行わないと、2つのモデルの違いが見落とされがちです。

私はこれが議論を解決することを望みますが、おそらく解決しないでしょう。

編集3

私はこの投稿に少し夢中かもしれません。おそらく、Javaのメーカーが誤って誤った情報を広めたと思うからでしょう。ポインタに「参照」という単語を使用する代わりに、別の何かを使用していた場合(たとえば、ディングルベリー)、問題はなかったでしょう。「Javaはdingleberriesを参照ではなく値で渡す」と言うこともでき、混乱する人はいません。

これが、Java開発者だけが問題を抱えている理由です。彼らは「リファレンス」という言葉を見て、それが何を意味するのかを正確に理解していると考えているので、反対する議論を検討することすらしません。

とにかく、古い投稿のコメントに気付いたので、私は本当に好きな風船の例えを作りました。そのため、私はいくつかのクリップアートを一緒に接着して、ポイントを説明するための漫画のセットを作ることにしました。

参照を値で渡す-参照への変更は呼び出し元のスコープには反映されませんが、オブジェクトへの変更は反映されます。これは、参照はコピーされますが、元のオブジェクトとコピーの両方が同じオブジェクトを参照しているためです。 値によるオブジェクト参照の受け渡し

参照渡し-参照のコピーはありません。単一の参照は、呼び出し元と呼び出される関数の両方で共有されます。参照またはオブジェクトのデータへの変更は、呼び出し元のスコープに反映されます。 参照渡し

編集4

このトピックに関する投稿で、Javaでのパラメーターの受け渡しの低レベルの実装について説明しました。これは、抽象的なアイデアを具体化するため、非常に役立ち、非常に役立ちます。ただし、私にとっての問題は、動作の技術的な実装ではなく、言語仕様に記述されている動作についてです。これは、Java言語仕様のセクション8.4.1からの抜粋です。

メソッドまたはコンストラクターが呼び出されると(§15.12)、実際の引数式の値は、メソッドまたはコンストラクターの本体を実行する前に、新しく宣言された型のパラメーター変数を初期化します。DeclaratorIdに表示される識別子は、メソッドまたはコンストラクターの本体で単純な名前として使用して、仮パラメーターを参照できます。

つまり、Javaはメソッドを実行する前に、渡されたパラメーターのコピーを作成します。大学でコンパイラを研究し、ほとんどの人と同じように、私が使用する「ドラゴンブック」でのコンパイラの本を。第1章の「値による呼び出し」と「参照による呼び出し」の説明は適切です。値による呼び出しの説明は、Java仕様と完全に一致しています。

90年代にコンパイラを研究していた頃、私は1986年の本の初版を使用しました。しかし、実際にはJavaについて言及している 2007年の第2版のコピーを見つけました「パラメータの受け渡しメカニズム」というラベルの付いたセクション1.6.6は、パラメータの受け渡しをかなりうまく説明しています。これは、Javaについて言及する「値による呼び出し」という見出しの下の抜粋です。

値による呼び出しでは、実際のパラメーターが評価され(式の場合)、またはコピーされます(変数の場合)。値は、呼び出されたプロシージャの対応する仮パラメーターに属する場所に配置されます。このメソッドはCおよびJavaで使用され、C ++や他のほとんどの言語で一般的なオプションです。


4
正直なところ、Javaはプリミティブ型に対してのみ値渡しであると言うことで、この答えを単純化できます。Objectから継承するものはすべて、参照渡しであり、参照は渡すポインターです。
スキューバスティーブ

1
@JuanMendes、私は例としてC ++を使用しました。他の言語の例を挙げましょう。「参照渡し」という用語は、C ++が登場するずっと前から存在していました。それは非常に具体的な定義を持つ教科書用語です。そして、当然のことながら、Javaは参照渡しされません。必要に応じてこの方法でこの用語を使い続けることができますが、使用法は教科書の定義と一致しません。それは単なるポインタへのポインタではありません。これは、「参照渡し」を可能にするために言語によって提供される構造です。私の例をじっくりと見てください。しかし、私の言葉ではなく、自分で調べてください。
Sanjeev

2
@AutomatedMike Javaを「参照渡し」として説明することも誤解を招くと思います。それらの参照は単なるポインタにすぎません。Sanjeevが価値を書いたように、参照とポインタは、Javaの作成者が何を使用するかに関係なく、独自の意味を持つ教科書の用語です。
JędrzejDudkiewicz

1
@ArtanisZeratul要点は微妙ですが、複雑ではありません。投稿した漫画をご覧ください。フォローするのはとても簡単だと感じました。Javaが値渡しであることは、単なる意見ではありません。それは、値渡しのテキストブック定義によって真実です。また、「常に価値があると言っている人」には、Javaの作成者であるJames Goslingのようなコンピュータ科学者も含まれます。私の投稿の「編集2」にある彼の本の引用をご覧ください。
Sanjeev

1
「参照渡し」はJava(およびJSのような他の言語)には存在しません。
newfolder

56

私の知る限り、Javaは値による呼び出ししか認識していません。つまり、プリミティブデータ型の場合はコピーを操作し、オブジェクトの場合はオブジェクトへの参照のコピーを操作します。ただし、いくつかの落とし穴があると思います。たとえば、これは機能しません:

public static void swap(StringBuffer s1, StringBuffer s2) {
    StringBuffer temp = s1;
    s1 = s2;
    s2 = temp;
}


public static void main(String[] args) {
    StringBuffer s1 = new StringBuffer("Hello");
    StringBuffer s2 = new StringBuffer("World");
    swap(s1, s2);
    System.out.println(s1);
    System.out.println(s2);
}

これは、Hello Worldではなく、Hello Worldを生成します。スワップ機能では、メインの参照に影響を与えないコピーを使用するためです。ただし、オブジェクトが不変でない場合は、たとえば次のように変更できます。

public static void appendWorld(StringBuffer s1) {
    s1.append(" World");
}

public static void main(String[] args) {
    StringBuffer s = new StringBuffer("Hello");
    appendWorld(s);
    System.out.println(s);
}

これにより、コマンドラインでHello Worldが入力されます。StringBufferをStringに変更すると、Stringは不変であるため、Helloだけが生成されます。例えば:

public static void appendWorld(String s){
    s = s+" World";
}

public static void main(String[] args) {
    String s = new String("Hello");
    appendWorld(s);
    System.out.println(s);
}

ただし、次のようにStringのラッパーを作成すると、Stringで使用できるようになります。

class StringWrapper {
    public String value;

    public StringWrapper(String value) {
        this.value = value;
    }
}

public static void appendWorld(StringWrapper s){
    s.value = s.value +" World";
}

public static void main(String[] args) {
    StringWrapper s = new StringWrapper("Hello");
    appendWorld(s);
    System.out.println(s.value);
}

編集:これは、2つの文字列を「追加」するときにStringBufferを使用する理由でもあると思います。これは、Stringのような不変オブジェクトではできない元のオブジェクトを変更できるためです。


スワップテストの+1-おそらく、参照による受け渡しによる参照の受け渡しを区別する最も簡単で関連性のある方法です。あなたは簡単に関数を書くことができますIFF swap(a, b)(1)スワップことab、発信者のPOVから、(2)のタイプに依存しない静的なタイピングが可能になる程度までである(別のタイプでそれを使用する意味はよりの宣言されたタイプの変更以外何も必要としないaとしb) 、および(3)呼び出し元がポインターまたは名前を明示的に渡す必要がない場合、言語は参照渡しをサポートします。
cHao

「プリミティブデータ型の場合はコピーを操作し、オブジェクトの場合はオブジェクトへの参照のコピーを操作します」-完全に記述されています!
ラウル・

55

いいえ、参照渡しではありません。

Javaは、Java言語仕様に従って値渡しされます。

メソッドまたはコンストラクターが呼び出されると(§15.12)、実際の引数式の値は、メソッドまたはコンストラクターの本体を実行する前に、新しく作成されたパラメーター変数(宣言された型のそれぞれ)を初期化します。DeclaratorIdに表示される識別子は、メソッドまたはコンストラクターの本体で単純な名前として使用して、仮パラメーターを参照できます


52

私の理解を4つの例を使って説明してみましょう。Javaは値渡しであり、参照渡しではありません

/ **

値渡し

Javaでは、すべてのパラメーターは値で渡されます。つまり、メソッド引数の割り当ては呼び出し元には見えません。

* /

例1:

public class PassByValueString {
    public static void main(String[] args) {
        new PassByValueString().caller();
    }

    public void caller() {
        String value = "Nikhil";
        boolean valueflag = false;
        String output = method(value, valueflag);
        /*
         * 'output' is insignificant in this example. we are more interested in
         * 'value' and 'valueflag'
         */
        System.out.println("output : " + output);
        System.out.println("value : " + value);
        System.out.println("valueflag : " + valueflag);

    }

    public String method(String value, boolean valueflag) {
        value = "Anand";
        valueflag = true;
        return "output";
    }
}

結果

output : output
value : Nikhil
valueflag : false

例2:

/ ** * *値渡し* * /

public class PassByValueNewString {
    public static void main(String[] args) {
        new PassByValueNewString().caller();
    }

    public void caller() {
        String value = new String("Nikhil");
        boolean valueflag = false;
        String output = method(value, valueflag);
        /*
         * 'output' is insignificant in this example. we are more interested in
         * 'value' and 'valueflag'
         */
        System.out.println("output : " + output);
        System.out.println("value : " + value);
        System.out.println("valueflag : " + valueflag);

    }

    public String method(String value, boolean valueflag) {
        value = "Anand";
        valueflag = true;
        return "output";
    }
}

結果

output : output
value : Nikhil
valueflag : false

例3:

/ **この「値渡し」には「参照渡し」の感覚があります

一部の人々は、プリミティブ型と「文字列」は「値渡し」であり、オブジェクトは「参照渡し」であると言います。

しかし、この例からは、参照が値として渡されていることを念頭に置いて、値による実際の受け渡しのみであることを理解できます。つまり、参照は値によって渡されます。これが変更できる理由であり、ローカルスコープの後も保持されます。ただし、元のスコープ外の実際の参照を変更することはできません。その意味は、PassByValueObjectCase2の次の例で示されています。

* /

public class PassByValueObjectCase1 {

    private class Student {
        int id;
        String name;
        public Student() {
        }
        public Student(int id, String name) {
            super();
            this.id = id;
            this.name = name;
        }
        public int getId() {
            return id;
        }
        public void setId(int id) {
            this.id = id;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        @Override
        public String toString() {
            return "Student [id=" + id + ", name=" + name + "]";
        }
    }

    public static void main(String[] args) {
        new PassByValueObjectCase1().caller();
    }

    public void caller() {
        Student student = new Student(10, "Nikhil");
        String output = method(student);
        /*
         * 'output' is insignificant in this example. we are more interested in
         * 'student'
         */
        System.out.println("output : " + output);
        System.out.println("student : " + student);
    }

    public String method(Student student) {
        student.setName("Anand");
        return "output";
    }
}

結果

output : output
student : Student [id=10, name=Anand]

例4:

/ **

例3で述べたもの(PassByValueObjectCase1.java)に加えて、元のスコープ外の実際の参照を変更することはできません。」

注:のコードを貼り付けていませんprivate class Student。のクラス定義StudentはExample3と同じです。

* /

public class PassByValueObjectCase2 {

    public static void main(String[] args) {
        new PassByValueObjectCase2().caller();
    }

    public void caller() {
        // student has the actual reference to a Student object created
        // can we change this actual reference outside the local scope? Let's see
        Student student = new Student(10, "Nikhil");
        String output = method(student);
        /*
         * 'output' is insignificant in this example. we are more interested in
         * 'student'
         */
        System.out.println("output : " + output);
        System.out.println("student : " + student); // Will it print Nikhil or Anand?
    }

    public String method(Student student) {
        student = new Student(20, "Anand");
        return "output";
    }

}

結果

output : output
student : Student [id=10, name=Nikhil]

49

Javaで参照渡しすることはできません。明らかな方法の1つは、メソッド呼び出しから複数の値を返したい場合です。C ++の次のコードを検討してください。

void getValues(int& arg1, int& arg2) {
    arg1 = 1;
    arg2 = 2;
}
void caller() {
    int x;
    int y;
    getValues(x, y);
    cout << "Result: " << x << " " << y << endl;
}

Javaで同じパターンを使用したい場合もありますが、使用できません。少なくとも直接ではない。代わりに、次のようなことを行うことができます。

void getValues(int[] arg1, int[] arg2) {
    arg1[0] = 1;
    arg2[0] = 2;
}
void caller() {
    int[] x = new int[1];
    int[] y = new int[1];
    getValues(x, y);
    System.out.println("Result: " + x[0] + " " + y[0]);
}

以前の回答で説明したように、Javaでは配列へのポインターを値としてに渡しますgetValues。メソッドは配列要素を変更するため、これで十分です。慣例として、要素0には戻り値が含まれると想定しています。明らかに、これを他の方法で行うことができます。たとえば、コードを構造化してこれを不要にしたり、戻り値を含めたり、戻り値を設定したりできるクラスを構築したりできます。ただし、上記のC ++で使用できる単純なパターンはJavaでは使用できません。


48

私は仕様からより多くの詳細を追加するためにこの答えを提供したいと思いました。

まず、参照渡しと値渡しの違いは何ですか?

参照渡しとは、呼び出された関数のパラメーターが呼び出し元の渡された引数と同じであることを意味します(値ではなくID-変数自体)。

値渡しは、呼び出された関数のパラメーターが呼び出し元の渡された引数のコピーになることを意味します。

または、Wikipassから、参照渡しについて

参照渡しの評価(参照渡しとも呼ばれる)では、関数は、値のコピーではなく、引数として使用される変数への暗黙的な参照を受け取ります。これは通常、関数が引数として使用される変数を変更(つまり、代入)できることを意味します。これは、呼び出し元から見えるものです。

そして、値渡しの問題について

値による呼び出しでは、引数式が評価され、結果の値が関数[...]の対応する変数にバインドされます。関数またはプロシージャがパラメータに値を割り当てることができる場合、ローカルコピーのみが割り当てられます[...]。

次に、Javaがそのメソッド呼び出しで何を使用するかを知る必要があります。Java言語仕様の状態

メソッドまたはコンストラクターが呼び出されると(§15.12)、実際の引数式の値は、メソッドまたはコンストラクターの本体を実行する前に、新しく作成されたパラメーター変数(宣言された型のそれぞれ)を初期化します

したがって、引数の値を対応するパラメーター変数に割り当てます(またはバインドします)。

議論の価値は何ですか?

参照タイプについて考えてみましょう。JavaVirtual Machine仕様には、

参照型には、クラス型、配列型、インターフェース型の3種類があります。それらの値は、動的に作成されたクラスインスタンス、配列、またはインターフェイスを実装するクラスインスタンスまたは配列への参照です。

Java言語仕様も状態

参照値(多くの場合、参照のみ)は、これらのオブジェクトへのポインタであり、オブジェクトを参照しない特別なnull参照です。

(ある参照型の)引数の値は、オブジェクトへのポインターです。変数、参照型の戻り値の型を持つメソッドの呼び出し、およびインスタンス作成式(new ...)はすべて参照型の値に解決されることに注意してください。

そう

public void method (String param) {}
...
String var = new String("ref");
method(var);
method(var.toString());
method(new String("ref"));

Stringインスタンスへの参照の値はすべて、メソッドの新しく作成されたパラメータにバインドされますparam。これは、正確に値渡しの定義が説明するものです。そのため、Javaは値渡しです

参照に従ってメソッドを呼び出したり、参照されたオブジェクトのフィールドにアクセスしたりできるという事実は、会話とはまったく関係ありません。参照渡しの定義は

これは通常、関数が引数として使用される変数を変更(つまり、代入)できることを意味します。これは、呼び出し元から見えるものです。

Javaでは、変数を変更することは、変数を再割り当てすることを意味します。Javaでは、メソッド内で変数を再割り当てした場合、呼び出し元に気付かれません。変数が参照するオブジェクトの変更は、まったく別の概念です。


プリミティブ値は、Java仮想マシン仕様(こちら)でも定義されています。タイプの値は、適切にエンコードされた対応する整数値または浮動小数点値です(8、16、32、64などのビット)。


42

Javaでは参照のみが渡され、値によって渡されます。

Java引数はすべて値によって渡されます(参照はメソッドで使用されるときにコピーされます)。

プリミティブ型の場合、Javaの動作は単純です。値はプリミティブ型の別のインスタンスにコピーされます。

オブジェクトの場合も同様です。オブジェクト変数は、「new」キーワードを使用して作成されたオブジェクトのアドレスのみを保持するポインター(バケット)であり、プリミティブ型のようにコピーされます。

動作は、プリミティブ型とは異なるように見えます。コピーされたオブジェクト変数には、同じアドレス(同じオブジェクトへ)が含まれているためです。オブジェクトのコンテンツ/メンバーは、メソッド内で変更され、後で外部にアクセスする可能性があります。これにより、(含む)オブジェクト自体が参照によって渡されたように見えます。

「文字列」オブジェクトは、「オブジェクトは参照によって渡される」という都市の伝説に対する良い反例のように見えます。

実際には、メソッドを使用して、引数として渡された文字列の値を更新することはできません。

文字列オブジェクト。変更できないfinalと宣言された配列によって文字を保持します。「new」を使用して、オブジェクトのアドレスのみを別のアドレスに置き換えることができます。「新規」を使用して変数を更新すると、変数は最初に値で渡されてコピーされたため、オブジェクトに外部からアクセスできなくなります。


それでは、オブジェクトに関してはbyRefであり、プリミティブに関してはbyValですか?
Mox

@moxをお読みください:オブジェクトは参照によって渡されません。これは原文です:String a = new String( "unchanged");
user1767316 2017年

1
@Aaron「参照渡し」は、「Javaで「参照」と呼ばれる型のメンバーである値を渡す」という意味ではありません。「参照」の2つの使用法には異なる意味があります。
philipxy 2017

2
かなり紛らわしい答え。参照渡しのメソッドで文字列オブジェクトを変更できないのは、文字列オブジェクトは設計上不変であり、などの操作を実行できないためですstrParam.setChar ( i, newValue )。そうは言っても、文字列は他と同様に値で渡され、Stringは非プリミティブ型であるため、その値はnewで作成されたものへの参照であり、String.intern()を使用してこれを確認できます。
zakmck 2017年

1
代わりに、param = "another string"(新しいString( "another string")と同じ)を介して文字列を変更することはできません。これは、paramの参照値( "another string"を指す)がメソッドの体。しかし、それは他のオブジェクトにも当てはまります。違いは、クラスインターフェースで許可されている場合、param.changeMe()を実行すると、paramの参照値自体にもかかわらず、paramがそのオブジェクトを指しているため、参照されている最上位オブジェクトが変更されることです。 (アドレス指定された場所、C用語で)メソッドからポップアップすることはできません。
zakmck 2017年

39

区別、またはおそらく私が元のポスターと同じ印象の下にあったので、Javaは常に値渡しです。Javaのすべてのオブジェクト(Javaの場合、プリミティブを除くすべて)は参照です。これらの参照は値で渡されます。


2
最後から2番目の文は非常に誤解を招くと思います。「Javaのすべてのオブジェクトが参照である」というのは正しくありません。参照であるのは、それらのオブジェクトへの参照だけです。
Dawood ibnカリーム2017年

37

以前に多くの人々が言及したように、Javaは常に値渡しです

違いを理解するのに役立つ別の例を次に示します(クラシックスワップの例):

public class Test {
  public static void main(String[] args) {
    Integer a = new Integer(2);
    Integer b = new Integer(3);
    System.out.println("Before: a = " + a + ", b = " + b);
    swap(a,b);
    System.out.println("After: a = " + a + ", b = " + b);
  }

  public static swap(Integer iA, Integer iB) {
    Integer tmp = iA;
    iA = iB;
    iB = tmp;
  }
}

プリント:

前:a = 2、b = 3
後:a = 2、b = 3

これは、iAとiBが、渡された参照と同じ値を持つ新しいローカル参照変数であるためです(それぞれ、aとbを指します)。したがって、iAまたはiBの参照を変更しようとすると、ローカルスコープのみが変更され、このメソッドの外部では変更されません。


32

Javaは値渡しのみを行います。これを検証する非常に簡単な例。

public void test() {
    MyClass obj = null;
    init(obj);
    //After calling init method, obj still points to null
    //this is because obj is passed as value and not as reference.
}
private void init(MyClass objVar) {
    objVar = new MyClass();
}

4
これは、Javaが値で渡されることを確認する最も明確で最も簡単な方法です。objnull)の値がへinitの参照ではなくに渡されましたobj
David Schwartz 2017年

31

私はいつもそれを「コピー渡し」と考えています。これは、プリミティブでも参照でも、値のコピーです。それがプリミティブである場合、それは値であるビットのコピーであり、それがオブジェクトである場合、それは参照のコピーです。

public class PassByCopy{
    public static void changeName(Dog d){
        d.name = "Fido";
    }
    public static void main(String[] args){
        Dog d = new Dog("Maxx");
        System.out.println("name= "+ d.name);
        changeName(d);
        System.out.println("name= "+ d.name);
    }
}
class Dog{
    public String name;
    public Dog(String s){
        this.name = s;
    }
}

java PassByCopyの出力:

name = Maxx
name = Fido

プリミティブラッパークラスと文字列は不変であるため、これらのタイプを使用する例は他のタイプ/オブジェクトと同じようには機能しません。


5
「コピー渡し」とは、値渡しの意味です。
TJクラウダー2016

@TJCrowder。「パスバイコピー」は、Javaの目的で同じ概念を表現するためのより適切な方法である可能性があります。意味論的には、Javaに必要な参照渡しに似たアイデアを意味づけるのが非常に難しいためです。
マイクげっ歯類

@mikerodent-すみません、フォローしていません。:-)「値渡し」と「参照渡し」は、これらのコンセプトの正しい用語です。新しいものを作成するのではなく、(人々が必要なときに定義を提供して)それらを使用する必要があります。上記の「コピーによる受け渡し」は「値による受け渡し」の意味ですが、実際には「コピーによる受け渡し」の意味が明確ではありません。何のコピーですか。値?(例:オブジェクト参照)オブジェクト自体?だから私はアートの条件に固執する。:-)
TJクラウダー

28

他の一部の言語とは異なり、Javaでは値渡しと参照渡しのどちらかを選択できません。すべての引数は値で渡されます。メソッド呼び出しは、プリミティブ値のコピー(intやdoubleの値など)とオブジェクトへの参照のコピーの2種類の値をメソッドに渡すことができます。

メソッドがプリミティブ型パラメーターを変更する場合、パラメーターを変更しても、呼び出し元のメソッドの元の引数値には影響しません。

オブジェクトに関しては、オブジェクト自体をメソッドに渡すことはできません。したがって、オブジェクトのreference(address)を渡します。この参照を使用して、元のオブジェクトを操作できます。

Javaがオブジェクトを作成して保存する方法:オブジェクトを作成するとき、オブジェクトのアドレスを参照変数に保存します。次のステートメントを分析してみましょう。

Account account1 = new Account();

「アカウントaccount1」は参照変数のタイプと名前、「=」は代入演算子、「new」はシステムに必要な容量を要求します。オブジェクトを作成するキーワードnewの右側にあるコンストラクターは、キーワードnewによって暗黙的に呼び出されます。作成されたオブジェクトのアドレス(「クラスインスタンス作成式」と呼ばれる式である右側の値の結果)は、代入演算子を使用して左側の値(名前と型が指定された参照変数)に割り当てられます。

オブジェクトの参照は値で渡されますが、メソッドはオブジェクトの参照のコピーを使用してそのパブリックメソッドを呼び出すことにより、参照されるオブジェクトと対話できます。パラメータに格納された参照は、引数として渡された参照のコピーであるため、呼び出されたメソッドのパラメータと呼び出しメソッドの引数は、メモリ内の同じオブジェクトを参照します。

配列オブジェクト自体ではなく配列への参照を渡すことは、パフォーマンス上の理由から理にかなっています。Javaではすべてが値によって渡されるため、配列オブジェクトが渡された場合、各要素のコピーが渡されます。大規模なアレイの場合、これは時間を浪費し、要素のコピーのためにかなりのストレージを消費します。

下の画像では、メインメソッドに2つの参照変数があることを確認できます(これらはC / C ++ではポインターと呼ばれます。この用語により、この機能を理解しやすくなります)。プリミティブ変数と参照変数はスタックメモリに保持されます(下の画像の左側)。配列1と配列2の参照変数「ポイント」(C / C ++プログラマーがそれを呼び出す)、またはヒープメモリ内のオブジェクト(これらの参照変数が保持する値はオブジェクトのアドレス)であるaおよびb配列への参照(下の画像の右側) 。

値渡しの例1

array1参照変数の値を引数としてreverseArrayメソッドに渡すと、メソッド内に参照変数が作成され、その参照変数は同じ配列を指し始めます(a)。

public class Test
{
    public static void reverseArray(int[] array1)
    {
        // ...
    }

    public static void main(String[] args)
    {
        int[] array1 = { 1, 10, -7 };
        int[] array2 = { 5, -190, 0 };

        reverseArray(array1);
    }
}

値渡しの例2

だから私たちが言うなら

array1[0] = 5;

reverseArrayメソッドでは、配列aを変更します。

reverseArrayメソッド(array2)には、配列cを指す別の参照変数があります。もし言うなら

array1 = array2;

reverseArrayメソッドでは、reverseArrayメソッドの参照変数array1が配列aへのポイントを停止し、配列c(2番目の画像の点線)へのポイントを開始します。

参照変数array2の値をメソッドreverseArrayの戻り値として返し、この値をmainメソッドの参照変数array1に割り当てると、mainのarray1が配列cをポイントし始めます。

それでは、今すぐに行ったことをすべて書きましょう。

public class Test
{
    public static int[] reverseArray(int[] array1)
    {
        int[] array2 = { -7, 0, -1 };

        array1[0] = 5; // array a becomes 5, 10, -7

        array1 = array2; /* array1 of reverseArray starts
          pointing to c instead of a (not shown in image below) */
        return array2;
    }

    public static void main(String[] args)
    {
        int[] array1 = { 1, 10, -7 };
        int[] array2 = { 5, -190, 0 };

        array1 = reverseArray(array1); /* array1 of 
         main starts pointing to c instead of a */
    }
}

ここに画像の説明を入力してください

そして、reverseArrayメソッドが終了したので、その参照変数(array1およびarray2)はなくなりました。つまり、メインメソッドarray1とarray2には、それぞれc配列とb配列を指す2つの参照変数しかありません。オブジェクト(配列)を参照している参照変数はありません。したがって、ガベージコレクションの対象となります。

メインのarray2の値をarray1に割り当てることもできます。array1はbを指し始めます。


27

ここでプログラミング言語に関するこの種の質問に特化したスレッドを作成しました。

Javaについても触れています。ここに短い要約があります:

  • Javaは値によってパラメータを渡します
  • 「値渡し」は、Javaでメソッドにパラメータを渡す唯一の方法です
  • パラメータとして指定されたオブジェクトのメソッドを使用すると、参照が元のオブジェクトを指すため、オブジェクトが変更されます。(そのメソッド自体がいくつかの値を変更する場合)

27

要するに、Javaオブジェクトにはいくつかの非常に独特な特性があります。

一般に、Javaは、プリミティブ型(持つintboolchardouble値によって直接渡される、など)。次に、Javaにはオブジェクト(から派生するすべてのものjava.lang.Object)があります。オブジェクトは、実際には常に参照(参照できないタッチ)を通じて処理されます。つまり、参照は通常は対象外なので、実際にはオブジェクトは参照によって渡されます。ただし、参照自体が値で渡されるため、どのオブジェクトがポイントされているかを変更することはできません。

これは奇妙で混乱しているように聞こえますか?Cが参照渡しと値渡しをどのように実装するかを考えてみましょう。Cでは、デフォルトの規則は値渡しです。void foo(int x)intを値で渡します。void foo(int *x)は不要な関数ですがint a、int:へのポインタですfoo(&a)。これを&演算子とともに使用して、変数アドレスを渡します。

これをC ++に持っていけば、リファレンスがあります。参照は、基本的に(このコンテキストでは)数式のポインター部分を隠す構文シュガーです:void foo(int &x)はによって呼び出されfoo(a)、コンパイラー自体が参照であることを認識しているため、非参照のアドレスをa渡す必要があります。Javaでは、オブジェクトを参照するすべての変数は実際には参照型であり、事実上、C ++などの細かい制御(および複雑さ)なしで、ほとんどの意図および目的のために参照による呼び出しを強制します。


Javaオブジェクトとそのリファレンスについての私の理解に非常に近いと思います。Javaのオブジェクトは、参照コピー(またはエイリアス)によってメソッドに渡されます。
MaxZoom、2015
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.