Pertanyaan Apakah Java "pass-by-reference" atau "pass-by-value"?


Saya selalu berpikir Java adalah pass-by-reference.

Namun, saya telah melihat beberapa posting blog (misalnya, blog ini) yang mengklaim bahwa itu bukan.

Saya tidak berpikir saya mengerti perbedaan yang mereka buat.

Apa penjelasannya?


5482


asal


Jawaban:


Java selalu pass-by-value. Sayangnya, mereka memutuskan untuk menyebut lokasi objek sebagai "referensi". Ketika kita melewati nilai suatu objek, kita melewati referensi untuk itu. Ini membingungkan bagi pemula.

Seperti ini:

public static void main(String[] args) {
    Dog aDog = new Dog("Max");
    // 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 
}

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
}

Dalam contoh di atas aDog.getName() masih akan kembali "Max". Nilai aDog dalam main tidak berubah dalam fungsi foo dengan Dog  "Fifi" sebagai referensi objek dilewatkan oleh nilai. Jika dilewatkan oleh referensi, maka aDog.getName() di main akan kembali "Fifi" setelah panggilan ke foo.

Juga:

public static void main(String[] args) {
    Dog aDog = new Dog("Max");
    foo(aDog);
    // when foo(...) returns, the name of the dog has been changed to "Fifi"
    aDog.getName().equals("Fifi"); // true
}

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

Dalam contoh di atas, Fifi adalah nama anjing setelah panggilan ke foo(aDog) karena nama objek sudah diatur di dalamnya foo(...). Setiap operasi itu foo tampil di d sedemikian rupa sehingga, untuk semua tujuan praktis, mereka dilakukan aDog sendiri (kecuali kapan d diubah untuk menunjuk ke yang berbeda Dog misalnya suka d = new Dog("Boxer")).


4734



Saya hanya memperhatikan Anda direferensikan artikel saya.

Spec Java mengatakan bahwa semua yang ada di Java adalah pass-by-value. Tidak ada yang namanya "pass-by-reference" di Java.

Kunci untuk memahami ini adalah sesuatu yang seperti itu

Dog myDog;

aku s tidak anjing; itu sebenarnya a penunjuk ke Anjing.

Apa artinya itu, adalah ketika Anda punya

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

Anda pada dasarnya melewati alamat dari yang dibuat Dog keberatan dengan foo metode.

(Saya katakan pada dasarnya karena Java pointer bukanlah alamat langsung, tetapi paling mudah untuk memikirkannya seperti itu)

Anggaplah Dog objek berada di alamat memori 42. Ini berarti kita melewati 42 ke metode.

jika Metode didefinisikan sebagai

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

mari kita lihat apa yang terjadi.

  • parameter someDog diatur ke nilai 42
  • pada baris "AAA"
    • someDog diikuti ke Dog itu menunjuk ke (yang Dog objek di alamat 42)
    • bahwa Dog (yang di alamat 42) diminta untuk mengubah namanya menjadi Max
  • pada baris "BBB"
    • yang baru Dog dibuat. Katakanlah dia ada di alamat 74
    • kami menetapkan parameter someDog menjadi 74
  • pada baris "CCC"
    • someDog diikuti ke Dog itu menunjuk ke (yang Dog keberatan pada alamat 74)
    • bahwa Dog (yang di alamat 74) diminta untuk mengubah namanya menjadi Rowlf
  • kemudian, kita kembali

Sekarang mari kita pikirkan tentang apa yang terjadi di luar metode:

Melakukan myDog perubahan?

Ada kuncinya.

Ingat itu myDog adalah penunjuk, dan bukan yang sebenarnya Dog, jawabannya adalah tidak. myDog masih memiliki nilai 42; itu masih menunjuk ke aslinya Dog(tetapi perhatikan bahwa karena garis "AAA", namanya sekarang "Max" - masih Anjing yang sama; myDogNilai tidak berubah.)

Ini sangat valid untuk mengikuti alamat dan ubah apa yang ada di ujungnya; itu tidak mengubah variabelnya.

Java berfungsi persis seperti C. Anda dapat menugaskan penunjuk, meneruskan pointer ke suatu metode, mengikuti penunjuk dalam metode dan mengubah data yang ditunjuk. Namun, Anda tidak dapat mengubah titik penunjuk itu.

Dalam C ++, Ada, Pascal dan bahasa lain yang mendukung pass-by-referensi, Anda benar-benar dapat mengubah variabel yang dilewatkan.

Jika Jawa memiliki semantik pass-by-reference, maka foo metode yang kami definisikan di atas akan berubah di mana myDog menunjuk ketika ditugaskan someDog di jalur BBB.

Pikirkan parameter referensi sebagai alias untuk variabel yang dilewatkan. Saat alias tersebut ditetapkan, begitu juga variabel yang diteruskan.


2637



Java selalu melewati argumen dengan nilai TIDAK dengan referensi.


Biar saya jelaskan ini melalui contoh:

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

Saya akan menjelaskan ini secara bertahap:

  1. Mendeklarasikan referensi yang diberi nama f tipe Foo dan menetapkannya ke objek tipe baru Foo dengan atribut "f".

    Foo f = new Foo("f");
    

    enter image description here

  2. Dari sisi metode, referensi tipe Foo dengan nama a dideklarasikan dan awalnya ditugaskan untuk null.

    public static void changeReference(Foo a)
    

    enter image description here

  3. Saat Anda memanggil metode changeReference, referensi a akan ditugaskan ke objek yang dilewatkan sebagai argumen.

    changeReference(f);
    

    enter image description here

  4. Mendeklarasikan referensi yang diberi nama b tipe Foo dan menetapkannya ke objek tipe baru Foo dengan atribut "b".

    Foo b = new Foo("b");
    

    enter image description here

  5. a = b adalah menugaskan ulang referensi a TIDAK f ke objek yang atributnya adalah "b".

    enter image description here


  6. Saat Anda menelepon modifyReference(Foo c) metode, referensi c dibuat dan ditetapkan ke objek dengan atribut "f".

    enter image description here

  7. c.setAttribute("c"); akan mengubah atribut dari objek referensi itu c menunjuk ke itu, dan itu objek yang sama referensi itu f menunjukkan itu.

    enter image description here

Saya harap Anda mengerti sekarang bagaimana cara mengirimkan objek sebagai argumen di Java :)


1388



Ini akan memberi Anda beberapa wawasan tentang bagaimana Java benar-benar bekerja sampai pada titik di diskusi Anda berikutnya tentang Java yang lewat referensi atau lewat nilai Anda hanya akan tersenyum :-)

Langkah pertama harap hapus dari pikiran Anda kata yang dimulai dengan 'p' "_ _ _ _ _ _ _", terutama jika Anda berasal dari bahasa pemrograman lain. Java dan 'p' tidak dapat ditulis dalam buku, forum, atau bahkan txt yang sama.

Langkah kedua, ingat bahwa ketika Anda melewatkan Objek ke dalam suatu metode, Anda melewatkan referensi Obyek dan bukan Objek itu sendiri.

  • Mahasiswa: Guru, apakah ini berarti bahwa Java adalah referensi-langsung?
  • Menguasai: Belalang, Tidak.

Sekarang pikirkan apa yang dimaksud dengan referensi / variabel Obyek adalah:

  1. Sebuah variabel menyimpan bit-bit yang memberi tahu JVM bagaimana menuju ke Objek yang direferensikan dalam memori (Heap).
  2. Ketika melewati argumen ke suatu metode Anda TIDAK melewatkan variabel referensi, tetapi salinan bit-bit dalam variabel referensi. Sesuatu seperti ini: 3bad086a. 3bad086a merupakan cara untuk menuju objek yang dilewatkan.
  3. Jadi Anda baru saja melewati 3bad086a bahwa itu adalah nilai referensi.
  4. Anda melewatkan nilai referensi dan bukan referensi itu sendiri (dan bukan objeknya).
  5. Nilai ini sebenarnya COPIED dan diberikan kepada metode.

Berikut ini (tolong jangan mencoba untuk mengkompilasi / menjalankan ini ...):

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. }

Apa yang terjadi?

  • Variabel orang dibuat di baris # 1 dan nol di awal.
  • Objek Orang baru dibuat di baris # 2, disimpan dalam memori, dan variabel orang diberikan referensi ke objek Person. Yaitu, alamatnya. Katakanlah 3bad086a.
  • Variabel orang memegang alamat Obyek dilewatkan ke fungsi sesuai baris # 3.
  • Sejalan # 4 Anda dapat mendengarkan suara keheningan
  • Periksa komentar di baris # 5
  • Sebuah metode variabel lokal -anotherReferenceToTheSamePersonObject- dibuat dan kemudian muncul keajaiban sesuai # 6:
    • Variabel / referensi orang disalin bit-demi-bit dan diteruskan ke anotherReferenceToTheSamePersonObject di dalam fungsi.
    • Tidak ada instance baru Person yang dibuat.
    • Keduanya "orang"dan"anotherReferenceToTheSamePersonObject"tahan nilai yang sama 3bad086a.
    • Jangan coba ini tapi orang == anotherReferenceToTheSamePersonObject akan benar.
    • Kedua variabel memiliki SALINAN IDENTIK dari referensi dan keduanya merujuk pada Objek Pribadi yang sama, Objek SAMA pada Heap dan BUKAN SALINAN.

Sebuah gambar bernilai seribu kata:

Pass by Value

Perhatikan bahwa panahRefferToTheSamePersonObject lainnya diarahkan ke Objek dan bukan ke arah orang variabel!

Jika Anda tidak mengerti maka percayalah pada saya dan ingat bahwa lebih baik mengatakan itu Java melewati nilai. Baik, lewat nilai referensi. Oh yah, lebih baik lagi pass-by-copy-of-the-variable-value! ;)

Sekarang merasa bebas untuk membenciku tetapi perhatikan yang diberikan ini tidak ada perbedaan antara tipe data dan Objek primitif yang lewat ketika berbicara tentang argumen metode.

Anda selalu memberikan salinan bit dari nilai referensi!

  • Jika itu adalah tipe data primitif, bit-bit ini akan berisi nilai dari tipe data primitif itu sendiri.
  • Jika itu Objek bit akan berisi nilai alamat yang memberitahu JVM bagaimana menuju ke Objek.

Java adalah pass-by-value karena di dalam suatu metode Anda dapat memodifikasi Objek yang direferensikan sebanyak yang Anda inginkan tetapi tidak peduli seberapa keras Anda mencoba, Anda tidak akan pernah dapat memodifikasi variabel yang diteruskan yang akan terus merujuk (tidak p _ _ _ _ _ _ _) Objek yang sama tidak peduli apa!


Fungsi changeName di atas tidak akan pernah dapat memodifikasi konten yang sebenarnya (nilai bit) dari referensi yang diteruskan. Dengan kata lain changeName tidak bisa membuat Person person merujuk ke Object lain.


Tentu saja Anda dapat memotongnya pendek dan hanya mengatakan itu Java adalah nilai kelulusan!


651



Java selalu lewat nilainya, tanpa pengecualian, pernah.

Jadi bagaimana bisa ada orang yang bingung dengan ini, dan percaya bahwa Java lewat referensi, atau berpikir mereka memiliki contoh Java bertindak sebagai pass by reference? Kuncinya adalah bahwa Jawa tak pernah menyediakan akses langsung ke nilai-nilai benda sendiri, di apa saja keadaan. Satu-satunya akses ke objek adalah melalui a referensi ke objek itu. Karena objek Java adalah selalu diakses melalui referensi, daripada secara langsung, adalah umum untuk berbicara tentang bidang dan variabel dan argumen metode sebagai sedang objek, ketika mereka hanya pedan referensi ke objek. Kebingungan berasal dari perubahan (secara tegas, salah) perubahan nomenklatur ini.

Jadi, saat memanggil metode

  • Untuk argumen primitif (int, long, dll.), lewat nilai adalah nilai sebenarnya dari primitif (misalnya, 3).
  • Untuk objek, pass by value adalah nilai dari referensi ke objek.

Jadi, jika Anda punya doSomething(foo) dan public void doSomething(Foo foo) { .. } kedua Foos telah disalin referensi titik itu ke objek yang sama.

Tentu saja, melewati nilai referensi ke objek terlihat sangat mirip (dan tidak dapat dibedakan dalam praktek dari) melewati objek dengan referensi.


561



Java memberikan referensi berdasarkan nilai.

Jadi, Anda tidak dapat mengubah referensi yang diteruskan.


278



Saya merasa seperti berdebat tentang "pass-by-reference vs pass-by-value" tidak sangat membantu.

Jika Anda mengatakan, "Java adalah pass-by-whatever (referensi / nilai)", dalam kedua kasus, Anda tidak memberikan jawaban yang lengkap. Berikut ini beberapa informasi tambahan yang diharapkan akan membantu memahami apa yang terjadi dalam memori.

Crash course di stack / heap sebelum kita masuk ke implementasi Java: Nilai pergi dan keluar dari tumpukan dengan cara yang teratur rapi, seperti tumpukan piring di kafetaria. Memori di tumpukan (juga dikenal sebagai memori dinamis) adalah serampangan dan tidak terorganisir. The JVM hanya menemukan ruang di mana pun itu bisa, dan membebaskannya sebagai variabel yang menggunakannya tidak lagi diperlukan.

Baik. Pertama, primitif lokal pergi ke tumpukan. Jadi kode ini:

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

hasil dalam ini:

primitives on the stack

Ketika Anda menyatakan dan memberi contoh suatu objek. Objek sebenarnya berada di heap. Apa yang terjadi di tumpukan? Alamat objek di heap. Pemrogram C ++ akan menyebutnya sebagai pointer, tetapi beberapa pengembang Java menentang kata "pointer". Terserah. Ketahuilah bahwa alamat objek berjalan di tumpukan.

Seperti ini:

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

a b*7ch aint one!

Array adalah sebuah objek, sehingga ia juga akan berjalan di heap. Dan bagaimana dengan objek dalam array? Mereka mendapatkan ruang heap mereka sendiri, dan alamat setiap objek masuk ke dalam array.

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

marx brothers

Jadi, apa yang dilewatkan ketika Anda memanggil metode? Jika Anda mengirimkan objek, yang sebenarnya Anda lewati adalah alamat objek. Beberapa orang mungkin mengatakan "nilai" dari alamat tersebut, dan beberapa mengatakan itu hanya referensi ke objek. Ini adalah asal usul perang suci antara "referensi" dan "nilai" pendukung. Apa yang Anda sebut itu tidak sepenting yang Anda mengerti bahwa apa yang diteruskan adalah alamat ke objek.

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

Satu String akan dibuat dan ruang untuk itu dialokasikan di heap, dan alamat ke string disimpan di stack dan diberikan identifier hisName, karena alamat String kedua sama dengan yang pertama, tidak ada String baru yang dibuat dan tidak ada ruang tumpukan baru yang dialokasikan, tetapi identifier baru dibuat di stack. Lalu kami memanggil shout(): bingkai tumpukan baru dibuat dan pengenal baru, name dibuat dan diberi alamat String yang sudah ada.

la da di da da da da

Jadi, nilai, referensi? Anda mengatakan "kentang".


198



Hanya untuk menunjukkan kontras, bandingkan berikut ini C ++ dan Jawa cuplikan:

Di C ++: Catatan: Kode buruk - kebocoran memori!  Tapi ini menunjukkan intinya.

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"
}

Di Jawa,

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 hanya memiliki dua jenis passing: berdasarkan nilai untuk tipe built-in, dan berdasarkan nilai pointer untuk jenis objek.


161



Java memberikan referensi ke objek berdasarkan nilai.


145