call-by-value
してはcall-by-reference
非常に重要な方法で異なります。(個人的にはcall-by-object-sharing
、最近call-by-value[-of-the-reference]
のセマンティクスを高レベルで説明し、基礎となる実装call-by-value
であるとの競合を引き起こさないため、最近ではを使用することを好みます。)
call-by-value
してはcall-by-reference
非常に重要な方法で異なります。(個人的にはcall-by-object-sharing
、最近call-by-value[-of-the-reference]
のセマンティクスを高レベルで説明し、基礎となる実装call-by-value
であるとの競合を引き起こさないため、最近ではを使用することを好みます。)
回答:
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
。
私はあなたが私の記事を参照したことに気づきました。
Java仕様では、Javaのすべてが値渡しであると述べています。Javaには「参照渡し」などはありません。
これを理解するための鍵は、
Dog myDog;
犬ではありません。それは実際には犬へのポインターです。
つまり、あなたが持っているときです
Dog myDog = new Dog("Rover");
foo(myDog);
基本的に、作成したオブジェクトのアドレスをメソッドに渡します。Dog
foo
(基本的に、Javaポインターは直接アドレスではないためですが、そのように考えるのが最も簡単です)
Dog
オブジェクトがメモリアドレス42にあるとします。これは、メソッドに42を渡すことを意味します。
メソッドが次のように定義されている場合
public void foo(Dog someDog) {
someDog.setName("Max"); // AAA
someDog = new Dog("Fifi"); // BBB
someDog.setName("Rowlf"); // CCC
}
何が起こっているのか見てみましょう。
someDog
は値42に設定されますsomeDog
Dog
itが指す(Dog
アドレス42のオブジェクト)に続きますDog
(アドレス42にある人)は名前をMaxに変更するように求められますDog
が作成されます。彼が住所74にいるとしましょうsomeDog
を74に割り当てますDog
それが指すit(Dog
アドレス74のオブジェクト)に従います。Dog
(アドレス74にある人)は名前をRowlfに変更するように求められます次に、メソッドの外で何が起こるかを考えてみましょう。
DID myDog
の変更を?
鍵があります。
これmyDog
はポインタであり、実際Dog
ではないことを念頭に置いて、答えはノーです。myDog
値はまだ42です。それはまだオリジナルを指していますDog
(ただし、「AAA」という行があるため、その名前は「Max」になりました-まだ同じDogです; myDog
の値は変更されていません。)
住所をフォローして、住所の最後にあるものを変更することは完全に有効です。ただし、変数は変更されません。
JavaはCとまったく同じように動作します。ポインターを割り当て、メソッドにポインターを渡し、メソッド内でポインターをたどり、ポイントされたデータを変更できます。ただし、そのポインタが指す場所を変更することはできません。
C ++、Ada、Pascal、および参照渡しをサポートするその他の言語では、渡された変数を実際に変更できます。
Javaに参照渡しのセマンティクスfoo
がmyDog
あった場合、上で定義したメソッドは、someDog
BBB行に割り当てたときに指し示していた場所を変更します。
参照パラメーターは、渡された変数のエイリアスであると考えてください。そのエイリアスが割り当てられると、渡された変数も同様に割り当てられます。
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");
}
}
これを段階的に説明します。
f
タイプの名前付き参照を宣言し、Foo
それにFoo
属性付きのタイプの新しいオブジェクトを割り当てます"f"
。
Foo f = new Foo("f");
メソッド側から、Foo
名前付きの型の参照a
が宣言され、最初に割り当てられnull
ます。
public static void changeReference(Foo a)
メソッドを呼び出すchangeReference
と、参照にa
は引数として渡されるオブジェクトが割り当てられます。
changeReference(f);
b
タイプの名前付き参照を宣言し、Foo
それにFoo
属性付きのタイプの新しいオブジェクトを割り当てます"b"
。
Foo b = new Foo("b");
a = b
参照に新しい割り当てを行いa
、ない f
、そのその属性であるオブジェクトの"b"
。
modifyReference(Foo c)
メソッドを呼び出すと、参照c
が作成され、属性を持つオブジェクトが割り当てられます"f"
。
c.setAttribute("c");
参照しているオブジェクトの属性を変更します。参照c
しているのは同じオブジェクトですf
。
引数としてオブジェクトを渡す方法がJavaでどのように機能するかを理解していただければ幸いです。
a
、同じオブジェクトを指している場合f
(およびオブジェクトの独自のコピーを決して取得していない場合f
)を使用して行われたオブジェクトへのa
変更f
は、両方とも同じオブジェクトで機能しているため、同様に変更する必要があります)、そのため、ある時点で、オブジェクトポイントのa
独自のコピーを取得する必要がありますf
。
これにより、参照渡しまたは値渡しのJavaに関する次のディスカッションでJavaが実際に機能する方法についての洞察が得られます。
ステップ1は、特に他のプログラミング言語から来ている場合は、「p」「_ _ _ _ _ _ _」で始まる単語を覚えておいてください。Javaと「p」を同じ本、フォーラム、またはtxtで書くことはできません。
ステップ2では、オブジェクトをメソッドに渡すときは、オブジェクト自体ではなく、オブジェクト参照を渡すことを覚えておいてください。
オブジェクトの参照/変数が何をする/するかを考えてみましょう:
以下では(これをコンパイル/実行しないでください...):
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. }
何が起こるのですか?
写真は千の言葉に値します:
anotherReferenceToTheSamePersonObject矢印は、変数personではなく、Objectに向けられていることに注意してください。
うまくいかなかった場合は、私を信頼して、Javaは値渡しであると言ったほうがよいことを覚えておいてください。さて、参照値で渡します。まあ、変数値のコピーによる受け渡しはさらに優れています!;)
ここで私を嫌いにしてください。ただし、メソッドの引数について話すとき、プリミティブデータ型とオブジェクトを渡すことには違いがないことに注意してください。
あなたは常にリファレンスの値のビットのコピーを渡します!
メソッド内では参照オブジェクトを好きなだけ変更できるのでJavaは値渡しですが、どんなに頑張っても、参照し続ける渡された変数を変更することはできません(p _ _ _ではなく) _ _ _ _)何があっても同じオブジェクト!
上記のchangeName関数は、渡された参照の実際のコンテンツ(ビット値)を変更することはできません。つまり、changeNameはPerson personに別のObjectを参照させることはできません。
もちろん、短くして、Javaは値渡しであると言え ます。
public void foo(Car car){ ... }
、car
でローカルにfoo
あり、オブジェクトのヒープ位置が含まれていますか?car
の値をで変更car = new Car()
すると、ヒープ上の別のオブジェクトを指しますか?car
のプロパティ値をで変更するcar.Color = "Red"
と、で示されるヒープ内のオブジェクトcar
が変更されます。また、C#でも同じですか?返信してください!ありがとう!
System.out.println(person.getName());
に立ち寄ってください。何が表示されますか?「トム」または「ジェリー」?これは私がこの混乱を防ぐのに役立つ最後のものです。
Javaは常に、例外なく、値渡しされ、これまで。
それで、誰もがこれによってまったく混乱する可能性があり、Javaが参照渡しであると信じるのか、または参照渡しとして機能するJavaの例があると思いますか?重要な点は、どのような状況においても、Java がオブジェクト自体の値に直接アクセスすることは決してないということです。オブジェクトへの唯一のアクセスは、そのオブジェクトへの参照を介したものです。Javaオブジェクトは直接ではなく常に参照を通じてアクセスされるため、フィールドや変数、メソッドの引数をオブジェクトであると説明することはよくあります。混乱は、命名法のこの(厳密に言えば、正しくない)変更から生じます。
したがって、メソッドを呼び出すとき
int
、long
など)の場合、値渡しはプリミティブの実際の値です(たとえば、3)。だから、あなたが持っている場合doSomething(foo)
とpublic void doSomething(Foo foo) { .. }
2つのFOOSをコピーした参照を、同じオブジェクトをポイントします。
当然、オブジェクトへの参照を値で渡すことは、参照でオブジェクトを渡すことと非常によく似ています(実際には区別できません)。
Javaは参照を値で渡します。
したがって、渡される参照を変更することはできません。
「参照渡しvs値渡し」について議論するのはあまり役に立ちません。
「Javaは何でも渡す(参照/値)」と言った場合、どちらの場合も完全な答えは得られません。記憶で何が起こっているのかを理解するのに役立つと思われる追加情報を以下に示します。
Java実装に到達する前に、スタック/ヒープのコースをクラッシュします。値は、カフェテリアでのプレートのスタックのように、整然とした方法でスタックに出入りします。ヒープ内のメモリ(動的メモリとも呼ばれます)は無計画で無秩序です。JVMはできる限りスペースを見つけ、それを使用する変数が不要になったときに解放します。
はい。まず、ローカルプリミティブがスタックに入ります。したがって、このコード:
int x = 3;
float y = 101.1f;
boolean amIAwesome = true;
この結果:
オブジェクトを宣言してインスタンス化するとき。実際のオブジェクトはヒープに置かれます。スタックには何が入っていますか?ヒープ上のオブジェクトのアドレス。C ++プログラマーはこれをポインターと呼びますが、一部のJava開発者は「ポインター」という単語に反対しています。なんでも。オブジェクトのアドレスがスタックに入れられることを知ってください。
そのようです:
int problems = 99;
String name = "Jay-Z";
配列はオブジェクトなので、ヒープにも適用されます。そして、配列内のオブジェクトはどうですか?それらは独自のヒープスペースを取得し、各オブジェクトのアドレスは配列内に入ります。
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
が作成されて、既存の文字列のアドレスが割り当てられます。
それで、値、参照?あなたは「ジャガイモ」と言います。
対比を示すために、次の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つのタイプの受け渡しのみがあります。組み込みタイプの値によるものと、オブジェクト型のポインターの値によるものです。
Dog **objPtrPtr
C ++の例にも追加します。これにより、ポインターが「指す」ものを変更できます。
基本的に、オブジェクトパラメータを再割り当てしても引数には影響しません。たとえば、
private void foo(Object bar) {
bar = null;
}
public static void main(String[] args) {
String baz = "Hah!";
foo(baz);
System.out.println(baz);
}
の"Hah!"
代わりに印刷されますnull
。これが機能する理由は、bar
がbaz
への参照であるの値のコピーであるためです"Hah!"
。それ自体が実際の参照である場合は、にfoo
再定義さbaz
れnull
ます。
まだ誰もバーバラ・リスコフについて言及していないとは信じられません。彼女は1974年にCLUを設計したとき、彼女はこの同じ用語の問題に走った、と彼女は長期考案共有によってコール(としても知られているオブジェクトを共有することにより通話やオブジェクトによって呼び出しを値がある値による呼び出し」のこの特定のケースのために)参照」。
問題の核心は、ワードということである参照表現では、単語の通常の意味とは全く異なる手段何か「参照渡し」参照のJavaインチ
通常、Java 参照では、オブジェクトへの参照を意味します。しかし、専門用語はプログラミング言語の理論からの参照/値によって渡され、変数を保持するメモリセルへの参照について話します。これは、まったく異なるものです。
Javaではすべてが参照であるため、Point pnt1 = new Point(0,0);
次のようなものがある場合:
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);
参考文献pnt1
とpnt2
されている値渡しトリッキーな方法を、今、あなたの参照手段pnt1
とは、pnt2
自分の持っているcopies
という名前arg1
とarg2
。だからpnt1
とarg1
ポイントを同じオブジェクトに。(pnt2
およびと同じarg2
)
ではtricky
方法:
arg1.x = 100;
arg1.y = 100;
tricky
メソッドの次
Point temp = arg1;
arg1 = arg2;
arg2 = temp;
ここでは、最初に、参照のように同じ場所temp
を指す新しいポイント参照を作成しarg1
ます。次に、参照のように同じ場所arg1
を指すように参照を移動しarg2
ます。最後にのような同じ場所arg2
を指しtemp
ます。
ここからの範囲tricky
方法がなくなって、あなたは、それ以上の言及にはアクセスできません:arg1
、arg2
、temp
。しかし、重要な注意点は、彼らが「生活の中で」されているときは、これらの参照を行うことすべてが永久に彼らがされているオブジェクトに影響しますある地点までを。
したがって、methodを実行した後tricky
、に戻るとmain
、次のような状況になります。
したがって、プログラムの完全な実行は次のようになります。
X1: 0 Y1: 0
X2: 0 Y2: 0
X1: 100 Y1: 100
X2: 0 Y2: 0
arg1.x = 1; arg1.y = 1; arg2.x = 2; arg2.y = 2;
。arg1がpnt2参照を保持し、arg2が現在pnt1参照を保持しているため、その印刷X1: 2 Y1: 2 X2: 1 Y2: 1
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は同じ値を持ち、したがって同じオブジェクトを指します。
リファレンス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
以下のようにをスローする可能性があります。
うまくいけば、これが役立ちます。
ボックスビューの外に出て、アセンブリまたはいくつかの低レベルのメモリ管理を見てみましょう。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の値を使用してメソッド/関数/プロシージャを呼び出す場合、言語とそのいくつかのメソッド呼び出しモードに応じて、変数をメソッドに渡す方法がいくつかあります。
いずれの場合も、値- 既存の値のコピー -が作成され、それを処理するのは受信側のメソッドです。メソッド内で "Foo"を書き込むと、それはEAXから読み取られるか、自動的に 逆参照されるか、または二重逆参照されます。プロセスは、言語の動作方法やFooの型によって決まります。これは、逆参照プロセスを回避するまで、開発者からは隠されています。そう基準は、ある値の基準は、(言語レベルで)処理されなければならない値であるため、表現します、。
これで、Fooをメソッドに渡しました。
Foo = 9
)を変更すると、値のコピーがあるため、ローカルスコープにのみ影響します。メソッドの内部からは、元の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
)。しかし、フーの値47
47
メソッドへのコピーでもあるため、メソッド内の1つだけで、グローバルに変更されませんでした。223
、メソッド内で変更すると、3または4と同じ騒乱が発生します(現在は不良値を指しているポインターが、ポインターとして再び使用されます)が、これはまだローカルです。 223がコピーされたため、問題。ただし、参照先の値Ref2Foo
(つまり223
)に到達し、ポイントされた値47
、たとえばに変更できる場合、49
Fooはグローバルに影響します。この場合、メソッドはコピーを取得します223
が、参照先47
が存在するのは1回だけであり、 to 49
は、すべてのRef2Foo
二重逆参照を間違った値に導きます。取るに足らない詳細を手間をかけて、参照渡しを行う言語でさえ値を関数に渡しますが、それらの関数は逆参照の目的で値を使用する必要があることを知っています。この参照としての値渡しは、実際には役に立たず、用語は参照渡しのみであるため、プログラマーから単に隠されています。
厳密な値渡しも無意味です。つまり、配列を引数としてメソッドを呼び出すたびに100Mバイトの配列をコピーする必要があるため、Javaを厳密に値渡しすることはできません。すべての言語はこの巨大な配列への参照を(値として)渡し、その配列をメソッド内でローカルに変更できる場合はコピーオンライトメカニズムを採用するか、(Javaが行うように)メソッドが配列をグローバルに(呼び出し元のビュー)といくつかの言語では、参照自体の値を変更できます。
つまり、簡単に言うと、Java自体の用語では、Javaは値渡しです。値は、実際の値または参照を表す値のいずれかです。
addr
型なし、型付き@
、および参照された変数を後で変更できます(ローカルにコピーされた変数を除く)。しかし、あなたがそうする理由はわかりません。あなたの例のその紙片(Object#24601)は参照であり、その目的はメモリ内の配列を見つけるのを助けることであり、それ自体には配列データが含まれていません。プログラムを再起動すると、その内容が前回の実行と同じであっても、同じ配列が異なるオブジェクトIDを取得する場合があります。
AtomicReference
、参照もそのターゲットも公開しませんが、代わりにターゲットに対して処理を行うメソッドを含みます。オブジェクトが渡されたコードが返されたら、AtomicReference
[作成者が直接参照を保持していた]を無効にして破棄する必要があります。それは適切なセマンティクスを提供しますが、遅くて厄介です。
Javaは値による呼び出しです
使い方
あなたは常にリファレンスの値のビットのコピーを渡します!
プリミティブデータタイプの場合、これらのビットにはプリミティブデータタイプ自体の値が含まれます。そのため、メソッド内のヘッダーの値を変更しても、外部の変更は反映されません。
それはのようなオブジェクト・データ型だ場合はFoo fooという=新しいはFoo()このケースではオブジェクトのアドレスのコピーは、ファイルのショートカットのように渡し、その後、我々はテキストファイルがあるとしabc.txtで\デスクトップ:Cを、我々はのショートカットを作ると仮定同じファイルをC:\ desktop \ abc-shortcut内に配置して、C:\ desktop \ abc.txtからファイルにアクセスし、「Stack Overflow」を書き込んでファイルを閉じ、もう一度ショートカットからファイルを開くと、次のようになります。「プログラマーが学習する最大のオンラインコミュニティーである」と書くと、ファイルの変更の合計は「スタックオーバーフローがプログラマーが学習する最大のオンラインコミュニティー」になりますつまり、同じファイルにアクセスするたびに、ファイルをどこから開いてもかまいません。ここでは、Fooをファイルとして想定し、fooを123hd7h(C:\ desktop \ abc.txtのような元のアドレス)に格納するとします。アドレスと234jdid(C:\ desktop \ abc-shortcutのようにコピーされたアドレスで、実際にはファイルの元のアドレスが含まれています)..理解を深めるために、ショートカットファイルを作成して感じてください。
これをカバーする素晴らしい答えがすでにあります。C ++での参照渡しと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 ++や他のほとんどの言語で一般的なオプションです。
私の知る限り、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のような不変オブジェクトではできない元のオブジェクトを変更できるためです。
swap(a, b)
(1)スワップことa
とb
、発信者のPOVから、(2)のタイプに依存しない静的なタイピングが可能になる程度までである(別のタイプでそれを使用する意味はよりの宣言されたタイプの変更以外何も必要としないa
としb
) 、および(3)呼び出し元がポインターまたは名前を明示的に渡す必要がない場合、言語は参照渡しをサポートします。
私の理解を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]
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では使用できません。
私は仕様からより多くの詳細を追加するためにこの答えを提供したいと思いました。
参照渡しとは、呼び出された関数のパラメーターが呼び出し元の渡された引数と同じであることを意味します(値ではなく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などのビット)。
Javaでは参照のみが渡され、値によって渡されます。
Java引数はすべて値によって渡されます(参照はメソッドで使用されるときにコピーされます)。
プリミティブ型の場合、Javaの動作は単純です。値はプリミティブ型の別のインスタンスにコピーされます。
オブジェクトの場合も同様です。オブジェクト変数は、「new」キーワードを使用して作成されたオブジェクトのアドレスのみを保持するポインター(バケット)であり、プリミティブ型のようにコピーされます。
動作は、プリミティブ型とは異なるように見えます。コピーされたオブジェクト変数には、同じアドレス(同じオブジェクトへ)が含まれているためです。オブジェクトのコンテンツ/メンバーは、メソッド内で変更され、後で外部にアクセスする可能性があります。これにより、(含む)オブジェクト自体が参照によって渡されたように見えます。
「文字列」オブジェクトは、「オブジェクトは参照によって渡される」という都市の伝説に対する良い反例のように見えます。
実際には、メソッドを使用して、引数として渡された文字列の値を更新することはできません。
文字列オブジェクト。変更できないfinalと宣言された配列によって文字を保持します。「new」を使用して、オブジェクトのアドレスのみを別のアドレスに置き換えることができます。「新規」を使用して変数を更新すると、変数は最初に値で渡されてコピーされたため、オブジェクトに外部からアクセスできなくなります。
strParam.setChar ( i, newValue )
。そうは言っても、文字列は他と同様に値で渡され、Stringは非プリミティブ型であるため、その値はnewで作成されたものへの参照であり、String.intern()を使用してこれを確認できます。
区別、またはおそらく私が元のポスターと同じ印象の下にあったので、Javaは常に値渡しです。Javaのすべてのオブジェクト(Javaの場合、プリミティブを除くすべて)は参照です。これらの参照は値で渡されます。
以前に多くの人々が言及したように、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の参照を変更しようとすると、ローカルスコープのみが変更され、このメソッドの外部では変更されません。
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();
}
obj
(null
)の値がへinit
の参照ではなくに渡されましたobj
。
私はいつもそれを「コピー渡し」と考えています。これは、プリミティブでも参照でも、値のコピーです。それがプリミティブである場合、それは値であるビットのコピーであり、それがオブジェクトである場合、それは参照のコピーです。
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
プリミティブラッパークラスと文字列は不変であるため、これらのタイプを使用する例は他のタイプ/オブジェクトと同じようには機能しません。
他の一部の言語とは異なり、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配列への参照(下の画像の右側) 。
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);
}
}
だから私たちが言うなら
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を指し始めます。
ここでは、プログラミング言語に関するこの種の質問に特化したスレッドを作成しました。
Javaについても触れています。ここに短い要約があります:
要するに、Javaオブジェクトにはいくつかの非常に独特な特性があります。
一般に、Javaは、プリミティブ型(持つint
、bool
、char
、double
値によって直接渡される、など)。次に、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 ++などの細かい制御(および複雑さ)なしで、ほとんどの意図および目的のために参照による呼び出しを強制します。