methods 参照型 ポインタ渡し - Javaは「参照渡し」または「渡し渡し」ですか?




15 Answers

私はちょうど私の記事を参照し気づいた。

Java仕様では、Javaのすべてが値渡しとなっています。 Javaでは「参照渡し」というようなことはありません。

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

Dog myDog;

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

それが意味することは、あなたが持っているときです

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に設定されます
  • ライン「AAA」
    • someDogは、それが指しているDogに続きます(アドレス42のDogオブジェクト)
    • そのDog (42番地のDog )は彼の名前をMaxに変更するよう頼まれています
  • ライン「BBB」
    • 新しいDogが作成されます。 彼が74番地にいるとしよう
    • パラメータsomeDogを74に代入します
  • ライン "CCC"
    • someDogはそれが指し示すDog (アドレス74のDogオブジェクト)に続き、
    • そのDog (住所74にあるDog )は彼の名前をRowlfに変更するように求められます
  • その後、私たちは戻る

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

myDog変更されましたか?

キーがあります。

myDogポインタであり、実際のDogではなく、答えはNOであることをmyDogておいてmyDogmyDogの値はまだ42です。 それはまだ元のDog指しています(ただし、 "AAA"という行のため、その名前は "Max"です - まだ同じDog; myDogの値は変更されていません)。

それ 、アドレスに従って 、それの終わりにあるものを変更することは完全に有効です。 変数を変更することはありません。

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

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

Javaが参照渡しのセマンティクスを持っていた場合、上で定義したfooメソッドは、 myDogがBBB行のsomeDogを割り当てたときに、 myDogが指していたところで変更されていmyDogた。

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

int string map

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

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

彼らが何をしているのか分かりません。

説明は何ですか?




これは、Javaが実際にどのように機能しているのか、いくつかの洞察を与えるでしょう。

第1ステップは、あなたが他のプログラミング言語から来た場合、特に「p」「_ _ _ _ _ _ _」で始まる単語をあなたの心から抹消してください。 Javaと 'p'は同じ本、フォーラム、またはtxtで記述することはできません。

ステップ2は、メソッドにObjectを渡すときにObject参照を渡し、Object自体を渡していないことを覚えておいてください。

  • Student :Master、これはJavaが参照渡しであることを意味しますか?
  • マスター :グラスホッパー、いいえ

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

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

以下のようにしてください(コンパイル/実行しないでください):

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の魔法になります:
    • 変数/参照は、ビットごとにコピーされ、関数内のanotherReferenceToTheSamePersonObjectに渡されます。
    • Personの新しいインスタンスは作成されません。
    • " person "と " anotherReferenceToTheSamePersonObject "は同じ値の3bad086aを保持します。
    • これを試してはいけませんが、person == anotherReferenceToTheSamePersonObjectがtrueになります。
    • どちらの変数も参照の同一コピーを持ち、同じオブジェクト、ヒープ上のSAMEオブジェクト、およびコピーではありません。

絵は千の言葉に値する:

anotherReferenceToTheSamePersonObject矢印は、オブジェクトに向けられ、可変の人に向けられていないことに注意してください。

あなたがそれを取得していない場合は、私だけを信頼し、 Javaが価値渡しであると言うことを忘れないでください。 さて、 参考値渡してください 。 まあ、変わった価値を渡すことは、 パスバイ・バイ・ザ・バリューです! ;)

今、私を嫌っても構いませんが、これを考慮すると、メソッドの引数について話すとき、プリミティブ型とオブジェクトを渡すことに違いはありません

あなたは常に参照の値のビットのコピーを渡します!

  • 基本データ型の場合、これらのビットには基本データ型自体の値が格納されます。
  • オブジェクトの場合、ビットには、JVMにオブジェクトへの到達方法を知らせるアドレスの値が格納されます。

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

上記のchangeName関数は、渡された参照の実際の内容(ビット値)を決して変更できません。 言い換えれば、changeNameはPerson personに別のObjectを参照させることはできません。

もちろん、あなたはそれを短くして、 Javaがパスバイバリューだと言うだけです!




Javaは参照によって値を渡します。

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




コントラストを表示するには、次の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種類の渡しがあります。




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

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

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

"Hah!"が印刷され"Hah!" null代わりに。 この理由は、 barbazの値のコピーであり、これはちょうど"Hah!"への参照"Hah!" 。 それが実際の参照そのものだった場合、 foobaznull再定義しました。




問題の核心は、ワードということである参照表現では「参照渡し」単語の通常の意味とは完全に異なる何かを意味リファレンス Javaでを。

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




参照は、使用する言語にかかわらず、常に表現された値です。

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

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

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

変数Fooがあり、Locationがメモリ内の47番目のバイトにあり、Valueが5であるとします。メモリ内の223番目のバイトにある別の変数Ref2Fooがあり、その値は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レジスタの1つにコピーされます
  4. 47スタックにプッシュします。
  5. 223がCPUレジスタの1つにコピーされます。
  6. 223はスタックにPUSHdを取得します。

上記の値(既存の値のコピー)を上回るすべてのケースで、それを処理する受信メソッドまで追加されました。メソッドの中に "Foo"を書くと、EAXから読み込まれるか、自動的に逆参照されるか、または二重逆参照されます。プロセスは言語の仕方やFooのタイプによって異なります。逆参照プロセスを回避するまで、これは開発者から隠されています。そう基準は、あるの基準は、(言語レベルで)処理されなければならない値であるため、表現します、。

今我々はメソッドにFooを渡しました:

  • 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は、プログラムの実行を別のメモリに書き込むことになるので、おそらくプログラムの実行をFUBARします。実行可能ファイルを保持することを目的とした領域も変更できますそれを書くと実行中のコードが変更されます(Fooは現在ではありません47)。しかし、Fooの価値47メソッド47のコピーでもあったため、メソッド内のものだけがグローバルに変更されませんでした。
  • 5.と6.の場合223、メソッド内で変更すると、3.や4.と同じ問題が発生します(今度はポインタとして使用され、ポインタとして再び使用されます)が、これはまだローカルです問題は、223がコピーされたためです。あなたが逆参照することができます場合はRef2Foo(つまり223)に到達し、先の尖った値を変更47するために、たとえば、49それがfooに影響を与えますグローバルこの場合には方法がのコピーを得たので、223しかし、参照47一度だけ存在し、ことを変更49すべてのRef2Foo二重逆参照を間違った値に導くことになります。

重要ではない細部、たとえ参照渡しの言語でさえ、関数に値を渡しますが、それらの関数は逆参照の目的で値を使用しなければならないことを知っています。この値渡しは、実際には役に立たず、用語が参照渡しでしかないため、プログラマから隠されているだけです。

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

だから、短いとJavaの独自の用語ではでは、Javaはある値渡し値は次のいずれかをすることができ、実際の値またはの表現である参照




私が知る限り、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ではなくWorld Helloにデータが設定されます。しかし、あなたのオブジェクトが不変でない場合は、例えば以下のように変更することができます:

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);
}

編集:私はあなたがStringのような不変のオブジェクトではできないオリジナルのオブジェクトを変更できるので、これは2つのStringを "追加"するときに、これもStringBufferを使う理由だと思います。




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:

/ **

Example3(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は価値のある呼び出しです。

使い方

  • あなたは常に参照の値のビットのコピーを渡します!

  • 基本データ型の場合、これらのビットには基本データ型自体の値が含まれています。そのため、メソッド内のheaderの値を変更すると、変更が外部に反映されません。

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




私が以前のポスターと同じ印象を受けていたのと同じように、覚えておいても差し支えないのは、Javaは常に価値があるということです。Javaのすべてのオブジェクト(Javaでは、プリミティブ以外のもの)は参照です。これらの参照は値渡しされます。




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();
}



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

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

プリミティブラッパークラスとストリングは不変ですので、これらの型を使用する例は他の型/オブジェクトと同じように動作しません。




いくつかの投稿への修正。

Cは参照渡しをサポートしていません。常に値渡しです。C ++は参照渡しをサポートしていますが、デフォルトではなく非常に危険です。

Javaの値が何であるかは関係ありません。オブジェクトのプリミティブまたはアドレス(大まかに)ですが、常に値によって渡されます。

Javaオブジェクトが参照渡しのように「振る舞う」場合、それは可変性のプロパティであり、メカニズムを渡すこととはまったく関係ありません。

私はこれがなぜとても混乱しているのか分かりません。なぜなら、多くのJava "プログラマ"が正式に訓練されておらず、実際に何がメモリ上で起こっているのか分からないからでしょうか?




JavaはVALUEによってパラメータを渡し、その値によってONLY

ショートストーリーを短縮するには:

C#から来た人には:"out"パラメータはありません。

パスカルからのもの:"var"パラメータはありません。

つまり、オブジェクト自体から参照を変更することはできませんが、オブジェクトのプロパティはいつでも変更できます。

回避策は、StringBuilder代わりにパラメータを使用する方法ですString。そして、あなたはいつも配列を使うことができます!




Related