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

6580

Saya selalu berpikir Java adalah pass-by-reference .

Namun, saya telah melihat beberapa posting blog (misalnya, blog ini ) yang mengklaim tidak.

Saya rasa saya tidak mengerti perbedaan yang mereka buat.

Apa penjelasannya?

pengguna4315
sumber
706
Saya percaya bahwa banyak kebingungan dalam masalah ini berkaitan dengan fakta bahwa orang yang berbeda memiliki definisi yang berbeda dari istilah "referensi". Orang-orang yang berasal dari latar belakang C ++ menganggap bahwa "referensi" harus berarti apa yang dimaksud dalam C ++, orang-orang dari latar belakang C menganggap "referensi" harus sama dengan "penunjuk" dalam bahasa mereka, dan seterusnya. Apakah benar untuk mengatakan bahwa Java lewat referensi benar-benar tergantung pada apa yang dimaksud dengan "referensi".
Gravity
119
Saya mencoba untuk secara konsisten menggunakan terminologi yang ditemukan di artikel Strategi Evaluasi . Perlu dicatat bahwa, meskipun artikel tersebut menunjukkan istilah-istilah yang sangat bervariasi oleh masyarakat, ia menekankan bahwa semantik untuk call-by-valuedan call-by-referenceberbeda dalam cara yang sangat penting . (Secara pribadi saya lebih suka menggunakan call-by-object-sharinghari-hari ini call-by-value[-of-the-reference], karena ini menggambarkan semantik pada tingkat tinggi dan tidak membuat konflik dengan call-by-value, yang merupakan implementasi yang mendasarinya.)
59
@ Gravity: Bisakah Anda pergi dan memberikan komentar Anda di papan iklan BESAR atau apa? Singkatnya, itu adalah masalah singkatnya. Dan ini menunjukkan bahwa semua ini adalah semantik. Jika kami tidak menyetujui definisi dasar dari sebuah referensi, maka kami tidak akan menyetujui jawaban untuk pertanyaan ini :)
MadConan
30
Saya pikir kebingungannya adalah "lewat referensi" versus "referensi semantik". Java adalah pass-by-value dengan semantik referensi.
spraff
11
@ Gravitasi, sementara Anda benar - benar benar bahwa orang-orang yang berasal dari C ++ secara naluriah akan memiliki set intuisi yang berbeda mengenai istilah "referensi", saya pribadi percaya tubuh lebih terkubur dalam "oleh". "Pass by" membingungkan karena sama sekali berbeda dari "Passing a" di Jawa. Dalam C ++, namun bahasa sehari-hari tidak. Dalam C ++ Anda bisa mengatakan "melewati referensi", dan dipahami bahwa itu akan lulus swap(x,y)tes .

Jawaban:

5844

Java selalu melewati nilai . Sayangnya, ketika kami melewatkan nilai suatu objek, kami meneruskan referensi untuk itu. Ini membingungkan bagi pemula.

Bunyinya seperti ini:

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
}

Dalam contoh di atas aDog.getName()masih akan kembali "Max". Nilai di aDogdalam maintidak diubah dalam fungsi foodengan Dog "Fifi"sebagai referensi objek dilewatkan oleh nilai. Jika disahkan dengan referensi, maka aDog.getName()in mainakan kembali "Fifi"setelah panggilan ke foo.

Juga:

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

Dalam contoh di atas, Fifiadalah nama anjing setelah panggilan ke foo(aDog)karena nama objek ditetapkan di dalamnya foo(...). Setiap operasi yang fooberkinerja dsedemikian rupa sehingga, untuk semua tujuan praktis, dilakukan aDog, tetapi tidak mungkin untuk mengubah nilai variabel aDogitu sendiri.

erlando
sumber
263
Bukankah ini sedikit membingungkan masalah dengan detail internal? Tidak ada perbedaan konseptual antara 'melewatkan referensi' dan 'melewati nilai referensi', dengan asumsi bahwa yang Anda maksud adalah 'nilai pointer internal ke objek'.
izb
383
Tetapi ada perbedaan yang halus. Lihatlah contoh pertama. Jika itu murni lewat referensi, aDog.name akan menjadi "Fifi". Bukan - referensi yang Anda dapatkan adalah referensi nilai yang jika ditimpa akan dipulihkan saat keluar dari fungsi.
erlando
300
@ Lorenzo: Tidak, di Jawa semuanya dilewati oleh nilai. Primitif dilewatkan oleh nilai, dan referensi objek dilewatkan oleh nilai. Objek itu sendiri tidak pernah diteruskan ke metode, tetapi objek selalu ada di heap dan hanya referensi ke objek yang diteruskan ke metode.
Esko Luontola
295
Upaya saya pada cara yang baik untuk memvisualisasikan objek yang lewat: Bayangkan sebuah balon. Memanggil fxn seperti mengikat tali kedua ke balon dan memberikan garis pada fxn. parameter = balon baru () akan memotong string itu dan membuat balon baru (tetapi ini tidak berpengaruh pada balon asli). parameter.pop () masih akan meletuskannya karena mengikuti string ke balon asli yang sama. Java dilewati oleh nilai, tetapi nilai yang dilewati tidak dalam, itu pada level tertinggi, yaitu primitif atau pointer. Jangan bingung dengan nilai pass-by-deep di mana objek sepenuhnya dikloning dan dilewati.
dhackner
150
Yang membingungkan adalah bahwa referensi objek sebenarnya adalah pointer. Pada awalnya SUN menyebut mereka pointer. Kemudian pemasaran memberi tahu bahwa "penunjuk" adalah kata yang buruk. Tetapi Anda masih melihat nomenklatur "benar" di NullPointerException.
Kontrak Prof. Falken dilanggar
3035

Saya baru saja memperhatikan Anda mereferensikan artikel saya .

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

Kunci untuk memahami ini adalah sesuatu seperti itu

Dog myDog;

adalah tidak Anjing; itu sebenarnya pointer ke Anjing.

Apa itu artinya, adalah ketika Anda memilikinya

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

Anda pada dasarnya menyampaikan alamatDog objek yang dibuat ke foometode.

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

Misalkan Dogobjek 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 someDogdiatur ke nilai 42
  • pada baris "AAA"
    • someDogdiikuti ke Dogitu menunjuk ke ( Dogobjek di alamat 42)
    • bahwa Dog(yang ada di alamat 42) diminta untuk mengubah namanya menjadi Max
  • pada baris "BBB"
    • yang baru Dogdibuat. Katakanlah dia di alamat 74
    • kami menetapkan parameter someDogke 74
  • pada baris "CCC"
    • someDog diikuti ke Dogmenunjuk ke ( Dogobjek di alamat 74)
    • bahwa Dog(yang di alamat 74) diminta untuk mengubah namanya menjadi Rowlf
  • lalu kita kembali

Sekarang mari kita pikirkan apa yang terjadi di luar metode:

Apakah ada myDogperubahan?

Ada kuncinya.

Ingatlah bahwa itu myDogadalah pointer , dan bukan yang sebenarnya Dog, jawabannya adalah TIDAK. myDogmasih memiliki nilai 42; masih menunjuk ke aslinya Dog(tapi perhatikan bahwa karena baris "AAA", namanya sekarang "Max" - masih Anjing yang sama; myDognilainya tidak berubah.)

Sangat valid untuk mengikuti alamat dan mengubah apa yang ada di akhir alamat itu; Namun, itu tidak mengubah variabel.

Java berfungsi persis seperti C. Anda dapat menetapkan sebuah pointer, meneruskan pointer ke suatu metode, mengikuti pointer dalam metode dan mengubah data yang diarahkan ke. Namun, Anda tidak dapat mengubah tempat penunjuk itu menunjuk.

Di C ++, Ada, Pascal dan bahasa lain yang mendukung pass-by-reference, Anda benar-benar dapat mengubah variabel yang diteruskan.

Jika Java memiliki semantik referensi-demi-referensi, foometode yang kami definisikan di atas akan berubah ke mana myDogmenunjuk ketika ditugaskan someDogpada baris BBB.

Pikirkan parameter referensi sebagai alias untuk variabel yang diteruskan. Ketika alias itu ditetapkan, begitu juga variabel yang dilewati.

Scott Stanchfield
sumber
164
Inilah sebabnya mengapa refrain umum "Java tidak memiliki pointer" sangat menyesatkan.
Beska
134
Anda salah, imho. "Ingat bahwa myDog adalah pointer, dan bukan Anjing yang sebenarnya, jawabannya adalah TIDAK. MyDog masih memiliki nilai 42; itu masih menunjuk ke Anjing yang asli." myDog memiliki nilai 42 tetapi argumen namanya sekarang berisi "Max", bukan "Rover" di // AAA line.
Özgür
180
Pikirkan seperti ini. Seseorang memiliki alamat Ann Arbor, MI (kampung halaman saya, GO BLUE!) Di selembar kertas yang disebut "annArborLocation". Anda menyalinnya di selembar kertas yang disebut "MyDestination". Anda dapat berkendara ke "MyDestination" dan menanam pohon. Anda mungkin telah mengubah sesuatu tentang kota di lokasi itu, tetapi itu tidak mengubah LAT / LON yang ditulis pada kertas mana pun. Anda dapat mengubah LAT / LON pada "myDestination" tetapi itu tidak mengubah "annArborLocation". Apakah itu membantu?
Scott Stanchfield
43
@Scott Stanchfield: Saya membaca artikel Anda sekitar setahun yang lalu dan itu sangat membantu menjernihkan semuanya bagi saya. Terima kasih! Bolehkah saya dengan rendah hati menyarankan sedikit tambahan: Anda harus menyebutkan bahwa sebenarnya ada istilah khusus yang menggambarkan bentuk "panggilan berdasarkan nilai di mana nilainya adalah referensi" yang ditemukan oleh Barbara Liskov untuk menggambarkan strategi evaluasi bahasa CLU-nya di 1974, untuk menghindari kebingungan seperti yang dituju oleh artikel Anda: panggilan dengan berbagi (kadang-kadang disebut panggilan dengan berbagi-objek atau sekadar panggilan dengan objek ), yang menggambarkan dengan sempurna semantiknya.
Jörg W Mittag
29
@Gevorg - Bahasa C / C ++ tidak memiliki konsep "pointer". Ada bahasa lain yang menggunakan pointer tetapi tidak mengizinkan jenis manipulasi pointer yang sama yang diizinkan oleh C / C ++. Java memiliki pointer; mereka hanya dilindungi dari kerusakan.
Scott Stanchfield
1741

Java selalu melewati argumen dengan nilai , BUKAN dengan referensi.


Izinkan saya menjelaskan ini melalui sebuah 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 dalam langkah-langkah:

  1. Mendeklarasikan referensi bernama ftipe Foodan menetapkan objek tipe baru Foodengan atribut "f".

    Foo f = new Foo("f");

    masukkan deskripsi gambar di sini

  2. Dari sisi metode, referensi tipe Foodengan nama adideklarasikan dan awalnya ditetapkan null.

    public static void changeReference(Foo a)

    masukkan deskripsi gambar di sini

  3. Saat Anda memanggil metode changeReference, referensi aakan diberikan objek yang dilewatkan sebagai argumen.

    changeReference(f);

    masukkan deskripsi gambar di sini

  4. Mendeklarasikan referensi bernama btipe Foodan menetapkan objek tipe baru Foodengan atribut "b".

    Foo b = new Foo("b");

    masukkan deskripsi gambar di sini

  5. a = bmembuat tugas baru ke referensi a, bukan f , dari objek yang atributnya adalah "b".

    masukkan deskripsi gambar di sini

  6. Saat Anda memanggil modifyReference(Foo c)metode, referensi cdibuat dan ditetapkan objek dengan atribut "f".

    masukkan deskripsi gambar di sini

  7. c.setAttribute("c");akan mengubah atribut dari objek yang cmenunjuk ke sana, dan itu adalah objek yang sama yang fmenunjuk ke sana.

    masukkan deskripsi gambar di sini

Saya harap Anda mengerti sekarang bagaimana meneruskan objek sebagai argumen bekerja di Jawa :)

Eng.Fouad
sumber
82
+1 Barang bagus. diagram yang bagus. Saya juga menemukan halaman ringkas yang bagus di sini adp-gmbh.ch/php/pass_by_reference.html OK saya akui itu ditulis dalam PHP, tetapi itu adalah prinsip memahami perbedaan yang menurut saya penting (dan bagaimana memanipulasi perbedaan itu untuk kebutuhanmu).
DaveM
5
@ Eng.Fouad Ini adalah penjelasan yang bagus tetapi jika amenunjuk ke objek yang sama dengan f(dan tidak pernah mendapatkan salinan sendiri dari objek fmenunjuk ke), setiap perubahan pada objek yang dibuat menggunakan aharus memodifikasi fjuga (karena mereka berdua bekerja dengan objek yang sama ), jadi pada titik tertentu aharus mendapatkan salinan sendiri dari objek fmenunjuk ke.
0x6C38
14
@ MR ketika 'a' menunjuk ke objek yang sama 'f' juga menunjuk ke, maka setiap perubahan yang dilakukan pada objek itu melalui 'a' juga dapat diamati melalui 'f', TAPI TIDAK MENGUBAH 'f'. 'f' masih menunjuk ke objek yang sama. Anda benar-benar dapat mengubah objek, tetapi Anda tidak pernah dapat mengubah apa yang ditunjukkan oleh 'f'. Ini adalah masalah mendasar yang karena beberapa alasan beberapa orang tidak dapat memahaminya.
Mike Braun
@ MikeBraun ... apa? Sekarang Anda telah membingungkan saya: S. Bukankah apa yang baru saja Anda tulis bertentangan dengan apa yang ditunjukkan 6. dan 7.?
Mesin Cuci Jahat
6
Ini adalah penjelasan terbaik yang saya temukan. Ngomong-ngomong, bagaimana dengan situasi dasarnya? Misalnya, argumen memerlukan tipe int, apakah masih meneruskan salinan variabel int ke argumen?
allenwang
732

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

Langkah pertama harap hapus dari pikiran Anda bahwa kata itu 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 Obyek ke metode Anda melewati referensi Obyek dan bukan Obyek itu sendiri.

  • Siswa : Guru, apakah ini berarti bahwa Java adalah pass-by-reference?
  • Master : Belalang, Tidak.

Sekarang pikirkan apa yang dilakukan referensi / variabel Obyek:

  1. Variabel memegang bit yang memberi tahu JVM bagaimana untuk sampai ke Object yang direferensikan dalam memori (Heap).
  2. Ketika meneruskan argumen ke metode, Anda TIDAK melewati variabel referensi, tetapi salinan bit dalam variabel referensi . Sesuatu seperti ini: 3bad086a. 3bad086a merupakan cara untuk sampai ke objek yang dilewati.
  3. Jadi Anda hanya 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 ke 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 diciptakan sejalan # 1 dan nol itu di awal.
  • Pengusaha Obyek baru dibuat sejalan # 2, yang tersimpan dalam memori, dan variabel orang diberi referensi ke objek Person. Itu adalah alamatnya. Katakanlah 3bad086a.
  • Variabel orang memegang alamat Objek dilewatkan ke fungsi sejalan # 3.
  • Di baris # 4 Anda dapat mendengarkan suara hening
  • Periksa komentar di baris # 5
  • Variabel lokal metode - anotherReferenceToTheSamePersonObject - dibuat dan kemudian muncul keajaiban di baris # 6:
    • Variabel / referensi orang disalin bit-by-bit dan diteruskan ke anotherReferenceToTheSamePersonObject dalam fungsi.
    • Tidak ada instance baru Person yang dibuat.
    • Baik " orang " dan " anotherReferenceToTheSamePersonObject " memiliki nilai yang sama dengan 3bad086a.
    • Jangan coba ini tetapi orang == anotherReferenceToTheSamePersonObject akan benar.
    • Kedua variabel memiliki SALIN IDENTIK referensi dan mereka berdua merujuk ke Objek Orang yang sama, Objek SAMA di Heap dan BUKAN SALINAN.

Sebuah gambar bernilai ribuan kata:

Lewati Nilai

Perhatikan bahwa panah lainReferenceToTheSamePersonObject diarahkan ke Objek dan bukan ke orang variabel!

Jika Anda tidak mendapatkannya maka percayalah pada saya dan ingat bahwa lebih baik mengatakan bahwa Java adalah nilai tambah . Nah, sampaikan nilai referensi . Oh well, bahkan lebih baik adalah pass-by-copy-of-the-variable-value! ;)

Sekarang merasa bebas untuk membenci saya tetapi perhatikan bahwa mengingat ini tidak ada perbedaan antara melewati tipe data primitif dan Objek ketika berbicara tentang argumen metode.

Anda selalu memberikan salinan bit dari nilai referensi!

  • Jika itu tipe data primitif, bit ini akan berisi nilai tipe data primitif itu sendiri.
  • Jika itu adalah Object, bit akan berisi nilai dari alamat yang memberi tahu JVM bagaimana untuk sampai ke Object.

Java adalah pass-by-value karena di dalam 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 dikirimkan yang akan terus referensi (bukan p _ _ _ _ _ _ _) Obyek yang sama tidak peduli apa!


Fungsi changeName di atas tidak akan pernah dapat mengubah konten aktual (nilai bit) dari referensi yang diteruskan. Dengan kata lain changeName tidak dapat membuat orang merujuk ke objek lain.


Tentu saja Anda dapat memotongnya dan katakan saja bahwa Java adalah nilai tambah!

Gevorg
sumber
5
Apakah maksud Anda pointer? .. Jika saya mendapatkannya dengan benar, di public void foo(Car car){ ... }, caradalah lokal ke foodan berisi lokasi tumpukan Obyek? Jadi jika saya mengubah carnilainya car = new Car(), itu akan menunjuk ke Objek yang berbeda di heap? dan jika saya mengubah nilai carproperti oleh car.Color = "Red", Object di heap yang ditunjuk oleh carakan dimodifikasi. Juga, sama di C #? Tolong dibalas! Terima kasih!
dpp
11
@domanokz Kau membunuhku, tolong jangan katakan kata itu lagi! ;) Perhatikan bahwa saya bisa menjawab pertanyaan ini tanpa mengatakan 'referensi' juga. Ini masalah terminologi dan membuatnya lebih buruk. Sayangnya, saya dan Scot memiliki pandangan berbeda tentang hal ini. Saya pikir Anda mengerti cara kerjanya di Jawa, sekarang Anda bisa menyebutnya lewat nilai, berbagi objek, dengan menyalin nilai variabel, atau merasa bebas untuk membuat sesuatu yang lain! Saya tidak terlalu peduli selama Anda mengetahui cara kerjanya dan apa yang ada dalam variabel tipe Object: hanya alamat PO Box! ;)
Marsellus Wallace
9
Kedengarannya seperti Anda baru saja melewati referensi ? Saya akan memperjuangkan fakta bahwa Java masih merupakan bahasa referensi yang disalin. Fakta bahwa itu adalah referensi yang disalin tidak mengubah terminologi. Kedua REFERENSI masih menunjuk ke objek yang sama. Ini adalah argumen purist ...
John Strickler
3
Jadi mampir di jalur teoritis # 9 System.out.println(person.getName());apa yang akan muncul? "Tom" atau "Jerry"? Ini adalah hal terakhir yang akan membantu saya mengatasi kebingungan ini.
TheBrenny
1
"Nilai referensi " adalah yang akhirnya menjelaskannya kepada saya.
Alexander Shubert
689

Java selalu lulus dengan nilai, tanpa pengecualian, selamanya .

Jadi bagaimana mungkin ada orang yang bingung dengan hal ini, dan percaya bahwa Java adalah pass by reference, atau berpikir mereka memiliki contoh Java bertindak sebagai pass by reference? Poin kuncinya adalah bahwa Java tidak pernah menyediakan akses langsung ke nilai-nilai objek itu sendiri , dalam keadaan apa pun . Satu-satunya akses ke objek adalah melalui referensi ke objek itu. Karena objek Java selalu diakses melalui referensi, bukan langsung, itu umum untuk berbicara tentang bidang dan variabel dan argumen metode sebagai objek , ketika secara Pedantis mereka hanya referensi ke objek .Kebingungan ini bermula dari perubahan nomenklatur ini (secara tegas, salah).

Jadi, saat memanggil metode

  • Untuk argumen primitif ( int,, longdll.), Nilai by pass adalah nilai aktual dari primitif (misalnya, 3).
  • Untuk objek, nilai by pass adalah nilai referensi ke objek .

Jadi jika Anda memiliki doSomething(foo)dan public void doSomething(Foo foo) { .. }dua Foos telah menyalin referensi yang mengarah ke objek yang sama.

Secara alami, memberikan nilai referensi ke objek terlihat sangat mirip (dan dalam praktiknya tidak bisa dibedakan dari) melewati objek dengan referensi.

SCdF
sumber
7
Karena nilai-nilai primitif tidak dapat diubah (seperti String), perbedaan antara kedua kasus tidak benar-benar relevan.
Paŭlo Ebermann
4
Persis. Untuk semua yang Anda tahu melalui perilaku JVM yang dapat diamati, primitif dapat dilewatkan dengan referensi dan bisa hidup di tumpukan. Mereka tidak, tapi itu sebenarnya tidak dapat diamati dengan cara apa pun.
Gravity
6
primitif tidak berubah? apakah itu baru di Java 7?
user85421
4
Pointer tidak berubah, primitif pada umumnya bisa berubah. String juga bukan primitif, itu adalah objek. Selain itu, struktur yang mendasari String adalah array yang bisa berubah. Satu-satunya hal yang tidak dapat diubah tentang itu adalah panjangnya, yang menjadi sifat alami dari array.
kingfrito_5005
3
Ini adalah jawaban lain yang menunjukkan sifat semantik dari argumen tersebut. Definisi referensi yang diberikan dalam jawaban ini akan membuat Java "pass-by-reference." Penulis pada dasarnya mengakui sebanyak dalam paragraf terakhir dengan menyatakan bahwa ia "tidak dapat dibedakan dalam praktik" dari "pass-by-reference." Saya ragu OP bertanya karena keinginan untuk memahami implementasi Java tetapi lebih untuk memahami bagaimana menggunakan Java dengan benar. Jika itu tidak bisa dibedakan dalam praktik, maka tidak ada gunanya merawat dan bahkan memikirkannya akan membuang-buang waktu.
Loduwijk
331

Java melewati referensi berdasarkan nilai.

Jadi, Anda tidak dapat mengubah referensi yang diteruskan.

ScArcher2
sumber
27
Tetapi hal yang terus berulang "Anda tidak dapat mengubah nilai objek yang dilewatkan dalam argumen" jelas salah. Anda mungkin tidak dapat membuatnya merujuk ke objek yang berbeda, tetapi Anda dapat mengubah kontennya dengan memanggil metode mereka. IMO ini berarti Anda kehilangan semua manfaat referensi, dan tidak mendapatkan jaminan tambahan.
Timmmm
34
Saya tidak pernah mengatakan "Anda tidak dapat mengubah nilai objek yang dilewatkan dalam argumen". Saya akan mengatakan "Anda tidak dapat mengubah nilai referensi objek yang diteruskan sebagai argumen metode", yang merupakan pernyataan yang benar tentang bahasa Java. Jelas Anda dapat mengubah keadaan objek (selama itu tidak berubah).
ScArcher2
20
Ingatlah bahwa Anda tidak dapat benar-benar melewatkan objek di java; benda-benda tetap di tumpukan. Pointer ke objek dapat dilewati (yang disalin ke bingkai tumpukan untuk metode yang disebut). Jadi Anda tidak pernah mengubah nilai pass-in (pointer), tetapi Anda bebas untuk mengikutinya dan mengubah hal di heap yang ditunjuknya. Itu nilai tambah.
Scott Stanchfield
10
Kedengarannya seperti Anda baru saja melewati referensi ? Saya akan memperjuangkan fakta bahwa Java masih merupakan bahasa referensi yang disalin. Fakta bahwa itu adalah referensi yang disalin tidak mengubah terminologi. Kedua REFERENSI masih menunjuk ke objek yang sama. Ini adalah argumen purist ...
John Strickler
3
Java tidak melewatkan objek, ia meneruskan nilai pointer ke objek. Ini menciptakan penunjuk baru ke lokasi memori objek asli dalam variabel baru. Jika Anda mengubah nilai (alamat memori yang ditunjukkannya) dari variabel penunjuk ini dalam suatu metode, maka penunjuk asli yang digunakan dalam pemanggil metode tetap tidak dimodifikasi. Jika Anda memanggil parameter referensi maka fakta bahwa itu adalah salinan dari referensi asli, bukan referensi asli itu sendiri sehingga sekarang ada dua referensi ke objek berarti bahwa itu pass-by-value
theferrit32
238

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

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

Crash course on stack / heap sebelum kita sampai ke implementasi Java: Nilai-nilai berjalan dan keluar dari stack dengan cara tertib yang bagus, seperti tumpukan piring di kafetaria. Memori dalam tumpukan (juga dikenal sebagai memori dinamis) adalah acak dan tidak terorganisir. JVM hanya menemukan ruang di mana pun ia bisa, dan membebaskannya karena variabel yang menggunakannya tidak lagi diperlukan.

Baik. Pertama, primitif lokal menggunakan stack. Jadi kode ini:

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

hasil dalam ini:

primitif di tumpukan

Saat Anda mendeklarasikan dan membuat instance objek. Objek aktual berjalan di heap. Apa yang terjadi di tumpukan? Alamat objek pada heap. Pemrogram C ++ akan menyebutnya pointer, tetapi beberapa pengembang Java menentang kata "pointer". Masa bodo. Ketahuilah bahwa alamat objek berada di tumpukan.

Seperti itu:

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

ab * 7ch tidak satu pun!

Array adalah objek, jadi ia berjalan di heap juga. Dan bagaimana dengan objek dalam array? Mereka mendapatkan ruang tumpukan 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");

saudara marx

Jadi, apa yang dilewatkan ketika Anda memanggil suatu metode? Jika Anda memasukkan 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 mula perang suci antara para pendukung "referensi" dan "nilai". Apa yang Anda sebut itu tidak sepenting yang Anda pahami 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 dibuat dan ruang untuk itu dialokasikan di heap, dan alamat ke string disimpan pada stack dan diberi pengenal hisName, karena alamat String kedua sama dengan yang pertama, tidak ada String baru yang dibuat dan tidak ada ruang heap baru yang dialokasikan, tetapi pengidentifikasi baru dibuat di stack. Kemudian kita memanggil shout(): bingkai stack baru dibuat dan pengidentifikasi baru, namedibuat dan diberi alamat dari String yang sudah ada.

la da di da da da da

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

cutmancometh
sumber
7
Namun, Anda harus menindaklanjuti dengan contoh yang lebih kompleks di mana fungsi muncul untuk mengubah variabel yang alamatnya memiliki referensi.
Brian Peterson
34
Orang tidak "menari di sekitar masalah nyata" tumpukan vs tumpukan, karena itu bukan masalah sebenarnya. Ini adalah detail implementasi terbaik, dan benar-benar salah. (Sangat mungkin bagi objek untuk hidup di stack; google "escape analysis". Dan sejumlah besar objek mengandung primitif yang mungkin tidak hidup di stack.) Masalah sebenarnya adalah persis perbedaan antara tipe referensi dan tipe nilai - khususnya, bahwa nilai variabel tipe referensi adalah referensi, bukan objek yang dirujuk.
cao
8
Ini adalah "detail implementasi" di mana Java tidak pernah diminta untuk benar-benar menunjukkan kepada Anda di mana objek tinggal di memori, dan pada kenyataannya tampaknya bertekad untuk menghindari membocorkan informasi itu. Itu bisa meletakkan objek di tumpukan, dan Anda tidak akan pernah tahu. Jika Anda peduli, Anda berfokus pada hal yang salah - dan dalam hal ini, itu berarti mengabaikan masalah sebenarnya.
cHao
10
Dan bagaimanapun, "primitif pergi di tumpukan" tidak benar. Variabel lokal primitif digunakan untuk stack. (Jika mereka belum dioptimalkan jauh, tentu saja.) Tapi kemudian, begitu juga variabel referensi lokal . Dan anggota primitif didefinisikan dalam suatu objek hidup di mana pun objek tinggal.
cHao
9
Setuju dengan komentar di sini. Stack / heap adalah masalah sampingan, dan tidak relevan. Beberapa variabel mungkin ada di stack, ada yang di memori statis (variabel statis), dan banyak tinggal di heap (semua variabel anggota objek). NONE dari variabel-variabel ini dapat dilewatkan dengan referensi: dari metode yang dipanggil TIDAK PERNAH mungkin untuk mengubah nilai variabel yang dilewatkan sebagai argumen. Oleh karena itu, tidak ada referensi by-pass di Jawa.
memancing pada
195

Untuk menunjukkan kontrasnya, bandingkan cuplikan C ++ dan Java berikut :

Dalam C ++: Catatan: Kode buruk - kebocoran memori! Tapi itu 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: dengan nilai untuk tipe bawaan, dan dengan nilai pointer untuk tipe objek.

Eclipse
sumber
7
+1 Saya juga akan menambahkan Dog **objPtrPtrcontoh C ++, dengan begitu kita bisa memodifikasi apa yang pointer "tunjuk".
Amro
1
Tetapi ini tidak menjawab untuk pertanyaan yang diberikan di Jawa. Faktanya, semua yang ada di metode Java adalah Pass By Value, tidak lebih.
tauitdnmd
2
Contoh ini hanya membuktikan bahwa java menggunakan pointer yang sangat setara dalam C no? Di mana nilai pass by dalam C pada dasarnya hanya baca? Pada akhirnya, seluruh utas ini tidak dapat dipahami
amdev
179

Java meneruskan referensi ke objek berdasarkan nilai.

John Channing
sumber
ini jawaban terbaik Pendek dan benar.
wol
166

Pada dasarnya, menetapkan kembali parameter Objek tidak mempengaruhi argumen, misalnya,

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

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

akan mencetak "Hah!"alih-alih null. Alasan mengapa ini berhasil adalah karena barmerupakan salinan dari nilai baz, yang hanya merupakan referensi "Hah!". Jika itu adalah referensi yang sebenarnya, maka fooakan didefinisikan ulang bazmenjadi null.

Hank Gay
sumber
8
Saya lebih suka mengatakan bahwa bilah adalah salinan baz referensi (atau baz alias), yang awalnya menunjuk ke objek yang sama.
MaxZoom
tidak ada sedikit perbedaan antara kelas String dan semua kelas lainnya?
Mehdi Karamosly
152

Saya tidak percaya bahwa belum ada yang menyebut Barbara Liskov. Ketika dia merancang CLU pada tahun 1974, dia mengalami masalah terminologi yang sama, dan dia menemukan istilah panggilan dengan berbagi (juga dikenal sebagai panggilan dengan berbagi objek dan panggilan dengan objek ) untuk kasus "panggilan berdasarkan nilai" khusus ini di mana nilainya referensi ".

Jörg W Mittag
sumber
3
Saya suka perbedaan dalam nomenklatur ini. Sangat disayangkan bahwa Java mendukung panggilan dengan berbagi untuk objek, tetapi tidak memanggil berdasarkan nilai (seperti halnya C ++). Java mendukung panggilan berdasarkan nilai hanya untuk tipe data primitif dan bukan tipe data komposit.
Derek Mahar
2
Saya benar-benar tidak berpikir kami membutuhkan istilah tambahan - itu hanya nilai per nilai untuk jenis nilai tertentu. Apakah menambahkan "panggil dengan primitif" menambah klarifikasi?
Scott Stanchfield
8
Panggil dengan Berbagi
wulfgarpro
Jadi saya dapat melewati referensi dengan berbagi beberapa objek konteks global, atau bahkan melewati objek konteks yang berisi referensi lain? Saya masih melewati nilai, tetapi setidaknya saya memiliki akses ke referensi yang dapat saya modifikasi dan membuatnya menunjuk ke sesuatu yang lain.
YoYo
1
@Sanjeev: call-by-object-sharing adalah kasus khusus nilai-demi-nilai. Namun, banyak orang berpendapat keras bahwa Java (dan bahasa serupa seperti Python, Ruby, ECMAScript, Smalltalk) adalah pass-by-reference. Saya lebih suka menyebutnya pass-by-value, juga, tetapi call-by-object-sharing tampaknya menjadi istilah yang masuk akal yang bahkan orang yang berpendapat bahwa itu bukan pass-by-value dapat menyetujuinya.
Jörg W Mittag
119

Inti masalahnya adalah bahwa kata referensi dalam ungkapan "pass by reference" berarti sesuatu yang sama sekali berbeda dari arti biasa dari kata referensi di Jawa.

Biasanya dalam referensi Java berarti referensi ke objek . Tetapi istilah teknis yang dilewatkan oleh referensi / nilai dari teori bahasa pemrograman berbicara tentang referensi ke sel memori yang memegang variabel , yang merupakan sesuatu yang sangat berbeda.

JacquesB
sumber
7
@Gevorg - Lalu apa itu "NullPointerException"?
Hot Licks
5
@ Hot: Sebuah pengecualian yang diberi nama sayang dari sebelum Java menetapkan terminologi yang jelas. Pengecualian semantik yang setara dalam c # disebut NullReferenceException.
JacquesB
4
Bagi saya selalu tampak bahwa penggunaan "referensi" dalam terminologi Java adalah pengaruh yang menghambat pemahaman.
Hot Licks
Saya belajar menyebutnya "pointer ke objek" sebagai "pegangan ke objek". Ini mengurangi ambiguitas.
moronkreacionz
86

Dalam java semuanya referensi, jadi ketika Anda memiliki sesuatu seperti: Point pnt1 = new Point(0,0);Java tidak mengikuti:

  1. Membuat objek Point baru
  2. Membuat referensi Titik baru dan menginisialisasi referensi ke titik (merujuk ke) pada objek Titik yang dibuat sebelumnya.
  3. Dari sini, melalui kehidupan objek Point, Anda akan mengakses objek itu melalui referensi pnt1. Jadi kita dapat mengatakan bahwa di Jawa Anda memanipulasi objek melalui referensi.

masukkan deskripsi gambar di sini

Java tidak lulus argumen metode dengan referensi; itu melewati mereka dengan nilai. Saya akan menggunakan contoh dari situs ini :

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

Aliran program:

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

Membuat dua objek Point yang berbeda dengan dua referensi yang berbeda terkait. masukkan deskripsi gambar di sini

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

Seperti yang diharapkan, output akan:

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

Pada baris ini 'pass-by-value' masuk ke dalam permainan ...

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

Referensi pnt1dan pnt2yang disahkan oleh nilai dengan metode rumit, yang berarti bahwa sekarang Anda referensi pnt1dan pnt2memiliki mereka copiesbernama arg1dan arg2Jadi pnt1dan arg1 poin ke objek yang sama. (Sama untuk pnt2dan arg2) masukkan deskripsi gambar di sini

Dalam trickymetode:

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

masukkan deskripsi gambar di sini

Selanjutnya dalam trickymetode

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

Di sini, Anda pertama kali membuat tempreferensi Titik baru yang akan menunjuk pada tempat yang sama seperti arg1referensi. Kemudian Anda memindahkan referensi arg1ke titik ke tempat yang sama seperti arg2referensi. Akhirnya arg2akan menunjuk ke tempat yang sama suka temp.

masukkan deskripsi gambar di sini

Dari sini lingkup trickymetode hilang dan Anda tidak memiliki akses lagi ke referensi: arg1, arg2, temp. Tapi catatan penting adalah bahwa semua yang Anda lakukan dengan referensi ini ketika mereka 'dalam hidup' secara permanen akan mempengaruhi objek yang mereka titik ke.

Jadi setelah menjalankan metode tricky, ketika Anda kembali ke main, Anda memiliki situasi ini: masukkan deskripsi gambar di sini

Jadi sekarang, eksekusi program sepenuhnya akan:

X1: 0         Y1: 0
X2: 0         Y2: 0
X1: 100       Y1: 100
X2: 0         Y2: 0
Srle
sumber
7
Di java ketika Anda mengatakan "Dalam java semuanya referensi" maksud Anda semua objek dilewatkan oleh referensi. Tipe data primitif tidak lulus dengan referensi.
Eric
Saya dapat mencetak nilai tukar dengan metode utama. dalam metode trickey, tambahkan pernyataan berikut ini arg1.x = 1; arg1.y = 1; arg2.x = 2; arg2.y = 2;, karena arg1 sekarang memegang referensi pnt2 dan arg2 memegang referensi sekarang pnt1, jadi, percetakannyaX1: 2 Y1: 2 X2: 1 Y2: 1
Shahid Ghafoor
85

Java selalu lewat nilai, bukan lewat referensi

Pertama-tama, kita perlu memahami apa yang melewati nilai dan melewati referensi.

Pass by value berarti Anda membuat salinan dalam memori dari nilai parameter aktual yang dilewatkan. Ini adalah salinan konten dari parameter aktual .

Pass by reference (juga disebut pass by address) berarti bahwa salinan alamat dari parameter aktual disimpan .

Terkadang Java bisa memberikan ilusi pass by reference. Mari kita lihat cara kerjanya dengan menggunakan contoh di bawah ini:

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

Output dari program ini adalah:

changevalue

Mari kita pahami langkah demi langkah:

Test t = new Test();

Seperti yang kita semua tahu itu akan membuat objek di heap dan mengembalikan nilai referensi kembali ke t. Sebagai contoh, misalkan nilai t adalah 0x100234(kita tidak tahu nilai internal JVM yang sebenarnya, ini hanya sebuah contoh).

ilustrasi pertama

new PassByValue().changeValue(t);

Ketika melewatkan referensi t ke fungsi, ia tidak akan langsung melewati nilai referensi aktual dari tes objek, tetapi akan membuat salinan t dan kemudian meneruskannya ke fungsi. Karena itu lewat nilai , itu melewati salinan variabel daripada referensi yang sebenarnya. Karena kita mengatakan nilai t adalah 0x100234, baik t dan f akan memiliki nilai yang sama dan karenanya mereka akan menunjuk ke objek yang sama.

ilustrasi kedua

Jika Anda mengubah sesuatu dalam fungsi menggunakan referensi f itu akan mengubah konten objek yang ada. Itu sebabnya kami mendapat output changevalue, yang diperbarui dalam fungsi.

Untuk memahami ini dengan lebih jelas, perhatikan contoh berikut:

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

Apakah ini akan melempar NullPointerException? Tidak, karena hanya melewati salinan referensi. Dalam kasus lewat referensi, itu bisa melempar NullPointerException, seperti yang terlihat di bawah ini:

ilustrasi ketiga

Semoga ini bisa membantu.

Ganesh
sumber
71

Referensi selalu merupakan nilai saat diwakili, tidak peduli bahasa apa yang Anda gunakan.

Untuk mendapatkan tampilan kotak di luar, mari kita lihat Majelis atau manajemen memori tingkat rendah. Pada level CPU referensi ke apa pun segera menjadi nilai jika itu ditulis ke memori atau ke salah satu register CPU. (Itulah sebabnya pointer adalah definisi yang baik. Ini adalah nilai, yang memiliki tujuan pada saat bersamaan).

Data dalam memori memiliki Lokasi dan di lokasi itu ada nilai (byte, kata, apa pun). Di Majelis, kami memiliki solusi yang mudah untuk memberikan Nama ke Lokasi tertentu (alias variabel), tetapi saat menyusun kode, assembler hanya mengganti Nama dengan lokasi yang ditentukan seperti browser Anda mengganti nama domain dengan alamat IP.

Sampai ke inti, secara teknis tidak mungkin untuk meneruskan referensi ke apa pun dalam bahasa apa pun tanpa mewakilinya (saat itu langsung menjadi nilai).

Katakanlah kita memiliki variabel Foo, yang Lokasi adalah di byte 47 dalam memori dan yang Nilai adalah 5. Kami memiliki variabel lain Ref2Foo yang di 223 byte dalam memori, dan nilainya akan 47. Ref2Foo ini mungkin menjadi variabel teknis , tidak secara eksplisit dibuat oleh program. Jika Anda hanya melihat 5 dan 47 tanpa informasi lain, Anda hanya akan melihat dua Nilai . Jika Anda menggunakannya sebagai referensi maka untuk mencapai 5kita harus bepergian:

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

Ini adalah cara kerja jump-tables.

Jika kita ingin memanggil metode / fungsi / prosedur dengan nilai Foo, ada beberapa cara untuk meneruskan variabel ke metode, tergantung pada bahasa dan beberapa mode pemanggilan metode:

  1. 5 disalin ke salah satu register CPU (mis. EAX).
  2. 5 menghasilkan PUSHd ke stack.
  3. 47 disalin ke salah satu register CPU
  4. 47 PUSHd ke tumpukan.
  5. 223 disalin ke salah satu register CPU.
  6. 223 mendapatkan PUSHd ke stack.

Dalam setiap kasus di atas nilai - salinan nilai yang ada - telah dibuat, sekarang tergantung pada metode penerima untuk menanganinya. Ketika Anda menulis "Foo" di dalam metode, itu dibaca dari EAX, atau secara otomatis ditinjau ulang , atau digandakan, proses tergantung pada bagaimana bahasa bekerja dan / atau apa yang menentukan jenis Foo. Ini disembunyikan dari pengembang sampai dia menghindari proses dereferencing. Jadi referensi adalah nilai ketika direpresentasikan, karena referensi adalah nilai yang harus diproses (pada tingkat bahasa).

Sekarang kami telah mengirimkan Foo ke metode:

  • dalam kasus 1. dan 2. jika Anda mengubah Foo ( Foo = 9) itu hanya memengaruhi lingkup lokal karena Anda memiliki salinan Nilai. Dari dalam metode kita bahkan tidak dapat menentukan di mana dalam memori Foo asli berada.
  • dalam kasus 3. dan 4. jika Anda menggunakan konstruksi bahasa default dan mengubah Foo ( Foo = 11), itu bisa mengubah Foo secara global (tergantung pada bahasa, mis. Java atau seperti Pascal's procedure findMin(x, y, z: integer;var m: integer); ). Namun jika bahasa tersebut memungkinkan Anda untuk menghindari proses dereference, Anda dapat mengubah 47, katakan saja 49. Pada titik itu Foo tampaknya telah diubah jika Anda membacanya, karena Anda telah mengubah pointer lokal ke sana. Dan jika Anda memodifikasi Foo ini di dalam metode ( Foo = 12) Anda mungkin akan FUBAR pelaksanaan program (alias. Segfault) karena Anda akan menulis ke memori yang berbeda dari yang diharapkan, Anda bahkan dapat memodifikasi area yang ditakdirkan untuk dapat dieksekusi program dan menulis untuk itu akan mengubah kode yang sedang berjalan (Foo sekarang tidak di 47). TAPI nilai Foo dari47tidak berubah secara global, hanya yang ada di dalam metode, karena 47juga merupakan salinan dari metode tersebut.
  • dalam kasus 5. dan 6. jika Anda memodifikasi 223di dalam metode itu menciptakan kekacauan yang sama seperti dalam 3. atau 4. (sebuah pointer, menunjuk ke nilai sekarang buruk, yang lagi-lagi digunakan sebagai pointer) tetapi ini masih lokal masalah, karena 223 disalin . Namun jika Anda dapat melakukan dereferensi Ref2Foo(yaitu 223), menjangkau dan memodifikasi nilai rujukan 47, katakan, untuk 49, itu akan mempengaruhi Foo secara global , karena dalam kasus ini metode mendapatkan salinan 223tetapi yang direferensikan 47hanya ada satu kali, dan mengubah itu untuk 49akan menyebabkan setiap Ref2Foodereferensi ganda ke nilai yang salah.

Nitpicking pada detail yang tidak penting, bahkan bahasa yang melakukan pass-by-reference akan memberikan nilai pada fungsi, tetapi fungsi tersebut tahu bahwa mereka harus menggunakannya untuk tujuan dereferencing. Pass-the-reference-as-value ini hanya disembunyikan dari programmer karena praktis tidak berguna dan terminologinya hanya pass-by-reference .

Nilai pass-by- ketat yang ketat juga tidak berguna, itu berarti bahwa array 100 Mbyte harus disalin setiap kali kita memanggil metode dengan array sebagai argumen, oleh karena itu Java tidak dapat dengan mudah melewati nilai demi nilai. Setiap bahasa akan meneruskan referensi ke array besar ini (sebagai nilai) dan menggunakan mekanisme copy-on-write jika array itu dapat diubah secara lokal di dalam metode atau memungkinkan metode (seperti yang dilakukan Java) untuk memodifikasi array secara global (dari tampilan pemanggil) dan beberapa bahasa memungkinkan untuk mengubah Nilai referensi itu sendiri.

Jadi, singkatnya dan dalam terminologi Java sendiri, Java adalah pass-by-value di mana nilainya bisa: nilai nyata atau nilai yang merupakan representasi dari referensi .

karatedog
sumber
1
Dalam bahasa dengan pass-by-reference, hal yang dilewati (referensi) adalah fana; penerima tidak "seharusnya" menyalinnya. Di Jawa, meneruskan array yang dilewatkan adalah "pengidentifikasi objek" - setara dengan selembar kertas yang mengatakan "Objek # 24601", ketika objek 24601 dibangun adalah sebuah array. Penerima dapat menyalin "Objek # 24601" di mana saja ia inginkan, dan siapa pun dengan selembar kertas yang mengatakan "Objek # 24601" dapat melakukan apa pun yang diinginkannya dengan salah satu elemen array. Pola bit yang dilewatkan tidak akan benar-benar mengatakan "Object # 24601", tentu saja, tapi ...
supercat
... titik kuncinya adalah bahwa penerima objek-id yang mengidentifikasi array dapat menyimpan objek-id itu di mana pun ia inginkan dan memberikannya kepada siapa pun yang diinginkannya, dan penerima apa pun akan dapat mengakses atau memodifikasi array kapan pun ia menginginkannya. inginkan. Sebaliknya, jika sebuah array dilewatkan dengan referensi dalam bahasa seperti Pascal yang mendukung hal-hal seperti itu, metode yang disebut bisa melakukan apa pun yang diinginkan dengan array, tetapi tidak dapat menyimpan referensi sedemikian rupa sehingga memungkinkan kode untuk memodifikasi array. setelah itu kembali.
supercat
Memang, dalam Pascal Anda bisa mendapatkan alamat dari setiap variabel, baik itu dilewatkan-oleh-referensi atau disalin secara lokal, addrapakah itu tidak diketik, @apakah itu dengan jenis, dan Anda dapat memodifikasi variabel yang dirujuk kemudian (kecuali yang disalin secara lokal). Tapi saya tidak mengerti mengapa Anda melakukan itu. Secarik kertas dalam contoh Anda (Objek # 24601) adalah referensi, tujuannya adalah untuk membantu menemukan array dalam memori, itu tidak mengandung data array itu sendiri. Jika Anda me-restart program Anda, array yang sama mungkin mendapatkan objek-id yang berbeda bahkan jika isinya akan sama seperti yang telah di jalankan sebelumnya.
karatedog
Saya pikir operator "@" bukan bagian dari Pascal standar, tetapi diimplementasikan sebagai ekstensi umum. Apakah ini bagian dari standar? Maksud saya adalah bahwa dalam bahasa dengan pass-by-ref yang benar, dan tidak ada kemampuan untuk membangun pointer non-sesaat ke objek sesaat, kode yang memegang referensi hanya untuk array, di mana saja di alam semesta, sebelum melewati array dengan referensi dapat mengetahui bahwa kecuali penerima "menipu" ia akan tetap memiliki satu-satunya referensi sesudahnya. Satu-satunya cara aman untuk mencapai itu di Jawa adalah dengan membangun objek sementara ...
supercat
... yang merangkum sebuah AtomicReferencedan tidak memaparkan referensi maupun targetnya, tetapi sebaliknya memasukkan metode untuk melakukan sesuatu terhadap target; begitu kode yang dilewati objek kembali, AtomicReference[yang penciptanya menyimpan referensi langsung] harus tidak valid dan ditinggalkan. Itu akan memberikan semantik yang tepat, tetapi itu akan lambat dan menjengkelkan.
supercat
70

Java adalah panggilan berdasarkan nilai

Bagaimana itu bekerja

  • Anda selalu memberikan salinan bit dari nilai referensi!

  • Jika itu tipe data primitif, bit-bit ini mengandung nilai tipe data primitif itu sendiri, Itu sebabnya jika kita mengubah nilai header di dalam metode maka itu tidak mencerminkan perubahan di luar.

  • Jika ini adalah tipe data objek seperti Foo foo = new Foo () maka dalam hal ini salinan alamat objek yang dilewati seperti pintasan file, misalkan kita memiliki file teks abc.txt di C: \ desktop dan anggaplah kita membuat pintasan dari file yang sama dan letakkan ini di dalam C: \ desktop \ abc-pintas jadi ketika Anda mengakses file dari C: \ desktop \ abc.txt dan tulis 'Stack Overflow' dan tutup file dan lagi Anda membuka file dari shortcut maka Anda tulis 'adalah komunitas online terbesar yang harus dipelajari oleh programmer' maka perubahan total file adalah 'Stack Overflow adalah komunitas online terbesar yang dipelajari oleh programmer'yang berarti tidak masalah dari mana Anda membuka file, setiap kali kami mengakses file yang sama, di sini kita dapat menganggap Foo sebagai file dan menganggap foo disimpan pada 123hd7h (alamat asli seperti C: \ desktop \ abc.txt ) address dan 234jdid (alamat yang disalin seperti C: \ desktop \ abc-shortcut yang sebenarnya berisi alamat asli file di dalamnya) .. Jadi untuk pemahaman yang lebih baik buat file shortcut dan rasakan ..

Himanshu arora
sumber
66

Sudah ada jawaban bagus yang membahas hal ini. Saya ingin membuat kontribusi kecil dengan membagikan contoh yang sangat sederhana (yang akan mengkompilasi) yang membedakan perilaku antara Pass-by-reference dalam c ++ dan Pass-by-value di Jawa.

Beberapa poin:

  1. Istilah "referensi" adalah kelebihan beban dengan dua makna yang terpisah. Di Jawa itu hanya berarti pointer, tetapi dalam konteks "Pass-by-reference" itu berarti pegangan ke variabel asli yang diteruskan.
  2. Java adalah Pass-by-value . Java adalah turunan dari C (di antara bahasa-bahasa lain). Sebelum C, beberapa (tetapi tidak semua) bahasa sebelumnya seperti FORTRAN dan COBOL mendukung PBR, tetapi C tidak. PBR memungkinkan bahasa-bahasa lain ini untuk membuat perubahan pada variabel yang dikirimkan di dalam sub-rutin. Untuk mencapai hal yang sama (yaitu mengubah nilai variabel di dalam fungsi), programmer C meneruskan pointer ke variabel menjadi fungsi. Bahasa yang terinspirasi oleh C, seperti Java, meminjam ide ini dan terus meneruskan pointer ke metode seperti yang dilakukan C, kecuali bahwa Java menyebut pointer-pointernya Referensi. Sekali lagi, ini adalah penggunaan kata "Referensi" yang berbeda dari pada "Pass-By-Reference".
  3. C ++ memungkinkan Pass-by-reference dengan mendeklarasikan parameter referensi menggunakan karakter "&" (yang kebetulan merupakan karakter yang sama dengan yang digunakan untuk menunjukkan "alamat variabel" di C dan C ++). Sebagai contoh, jika kita meneruskan sebuah pointer dengan referensi, parameter dan argumennya tidak hanya menunjuk ke objek yang sama. Sebaliknya, mereka adalah variabel yang sama. Jika satu diatur ke alamat yang berbeda atau ke nol, begitu pula yang lainnya.
  4. Dalam contoh C ++ di bawah ini saya meneruskan sebuah pointer ke string yang diakhiri dengan referensi . Dan dalam contoh Java di bawah ini saya meneruskan referensi Java ke sebuah String (sekali lagi, sama dengan pointer ke sebuah String) dengan nilai. Perhatikan output dalam komentar.

C ++ lulus dengan contoh referensi:

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 meneruskan "referensi Java" dengan contoh nilai

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

EDIT

Beberapa orang menulis komentar yang sepertinya mengindikasikan bahwa mereka tidak melihat contoh saya atau mereka tidak mendapatkan contoh c ++. Tidak yakin di mana putuskan, tetapi menebak contoh c ++ tidak jelas. Saya memposting contoh yang sama dalam pascal karena saya pikir pass-by-reference terlihat lebih bersih dalam pascal, tetapi saya bisa saja salah. Saya mungkin lebih membingungkan orang; Saya harap tidak.

Dalam pascal, parameter yang diteruskan oleh referensi disebut "parameter var". Dalam prosedur setToNil di bawah ini, harap perhatikan kata kunci 'var' yang mendahului parameter 'ptr'. Ketika sebuah pointer diteruskan ke prosedur ini, itu akan diteruskan dengan referensi . Perhatikan tingkah lakunya: ketika prosedur ini menyetel ptr ke nil (itu berbicara pascal untuk NULL), itu akan menetapkan argumen ke nil - Anda tidak dapat melakukannya di Jawa.

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.

EDIT 2

Beberapa kutipan dari "THE Java Programming Language" oleh Ken Arnold, James Gosling (orang yang menemukan Java) , dan David Holmes, bab 2, bagian 2.6.5

Semua parameter ke metode diteruskan "berdasarkan nilai" . Dengan kata lain, nilai-nilai variabel parameter dalam suatu metode adalah salinan dari invoker yang ditentukan sebagai argumen.

Dia melanjutkan untuk membuat titik yang sama tentang benda. . .

Anda harus mencatat bahwa ketika parameter adalah referensi objek, itu adalah referensi objek-bukan objek itu sendiri-yang dilewatkan "oleh nilai" .

Dan menjelang akhir bagian yang sama ia membuat pernyataan yang lebih luas tentang java yang hanya lewat nilai dan tidak pernah lewat referensi.

Bahasa pemrograman Java tidak melewatkan objek dengan referensi; itu melewati referensi objek dengan nilai . Karena dua salinan dari referensi yang sama merujuk ke objek aktual yang sama, perubahan yang dilakukan melalui satu variabel referensi dapat dilihat melalui yang lainnya. Tepat ada satu parameter mode lewat- lulus dengan nilai -dan yang membantu menjaga hal-hal sederhana.

Bagian buku ini memiliki penjelasan yang bagus tentang parameter yang lewat di Jawa dan perbedaan antara pass-by-reference dan pass-by-value dan oleh pencipta Java. Saya akan mendorong siapa pun untuk membacanya, terutama jika Anda masih tidak yakin.

Saya pikir perbedaan antara kedua model sangat halus dan kecuali Anda telah melakukan pemrograman di mana Anda benar-benar menggunakan pass-by-reference, mudah untuk kehilangan di mana dua model berbeda.

Saya harap ini menyelesaikan perdebatan, tapi mungkin tidak.

EDIT 3

Saya mungkin sedikit terobsesi dengan posting ini. Mungkin karena saya merasa bahwa pembuat Java secara tidak sengaja menyebarkan informasi yang salah. Jika alih-alih menggunakan kata "referensi" untuk petunjuk yang mereka gunakan sesuatu yang lain, katakanlah dingleberry, tidak akan ada masalah. Anda bisa mengatakan, "Java melewati dingleberry dengan nilai dan bukan dengan referensi", dan tidak ada yang akan bingung.

Itulah alasan mengapa hanya pengembang Java yang bermasalah dengan ini. Mereka melihat kata "referensi" dan berpikir mereka tahu persis apa artinya itu, sehingga mereka bahkan tidak repot-repot mempertimbangkan argumen yang berlawanan.

Ngomong-ngomong, saya perhatikan komentar di posting lama, yang membuat analogi balon yang sangat saya sukai. Sedemikian rupa sehingga saya memutuskan untuk merekatkan beberapa clip-art untuk membuat satu set kartun untuk menggambarkan intinya.

Melewati referensi dengan nilai - Perubahan ke referensi tidak tercermin dalam ruang lingkup pemanggil, tetapi perubahan pada objek. Ini karena referensi disalin, tetapi yang asli dan salinan merujuk ke objek yang sama. Melewati referensi Obyek Dengan Nilai

Lulus dengan referensi - Tidak ada salinan referensi. Referensi tunggal dibagikan oleh penelepon dan fungsi yang dipanggil. Setiap perubahan pada referensi atau data Objek tercermin dalam ruang lingkup pemanggil. Lewati dengan referensi

EDIT 4

Saya telah melihat posting pada topik ini yang menggambarkan implementasi level rendah dari passing parameter di Jawa, yang menurut saya hebat dan sangat membantu karena membuat ide abstrak menjadi konkret. Namun, bagi saya pertanyaannya lebih tentang perilaku yang dijelaskan dalam spesifikasi bahasa daripada tentang implementasi teknis dari perilaku tersebut. Ini adalah kutipan dari Spesifikasi Bahasa Jawa, bagian 8.4.1 :

Ketika metode atau konstruktor dipanggil (§15.12), nilai-nilai dari ekspresi argumen aktual menginisialisasi variabel parameter yang baru dibuat, masing-masing dari tipe yang dideklarasikan, sebelum eksekusi badan metode atau konstruktor. Identifier yang muncul di DeclaratorId dapat digunakan sebagai nama sederhana di tubuh metode atau konstruktor untuk merujuk ke parameter formal.

Yang berarti, java membuat salinan dari parameter yang diteruskan sebelum menjalankan suatu metode. Seperti kebanyakan orang yang belajar compiler di perguruan tinggi, saya menggunakan "The Dragon Buku" yang merupakan THE kompiler buku. Ini memiliki deskripsi yang baik tentang "Nilai-panggilan-" dan "Referensi-berdasarkan-panggilan" di Bab 1. Deskripsi panggilan-menurut-nilai cocok dengan Java Specs dengan tepat.

Kembali ketika saya mempelajari kompiler-di 90-an, saya menggunakan edisi pertama buku dari tahun 1986 yang pra-tanggal Jawa sekitar 9 atau 10 tahun. Namun, saya hanya menemukan salinan Edisi ke - 2 dari tahun 2007 yang sebenarnya menyebutkan Java! Bagian 1.6.6 yang berlabel "Mekanisme Passing Parameter" menggambarkan parameter yang lewat dengan cukup baik. Berikut adalah kutipan di bawah judul "Call-by-value" yang menyebutkan Java:

Dalam panggilan-oleh-nilai, parameter aktual dievaluasi (jika itu ekspresi) atau disalin (jika itu adalah variabel). Nilai ditempatkan di lokasi milik parameter formal yang sesuai dari prosedur yang disebut. Metode ini digunakan dalam bahasa C dan Java, dan merupakan pilihan umum dalam bahasa C ++, dan juga di sebagian besar bahasa lainnya.

Sanjeev
sumber
4
Jujur, Anda dapat menyederhanakan jawaban ini dengan mengatakan Java hanya memberikan nilai hanya untuk tipe primitif. Segala sesuatu yang mewarisi dari Objek secara efektif melewati referensi, di mana referensi adalah pointer yang Anda lewati.
Scuba Steve
1
@JuanMendes, saya baru saja menggunakan C ++ sebagai contoh; Saya bisa memberi Anda contoh dalam bahasa lain. Istilah "Lulus dengan referensi" ada jauh sebelum C ++ ada. Ini adalah istilah buku teks dengan definisi yang sangat spesifik. Dan menurut definisi, Java tidak lulus dengan referensi. Anda dapat tetap menggunakan istilah ini dengan cara ini jika Anda mau, tetapi penggunaan Anda tidak akan konsisten dengan definisi buku teks. Ini bukan hanya pointer ke pointer. Ini adalah konstruksi yang disediakan oleh bahasa untuk memungkinkan "Pass By Reference". Silakan perhatikan contoh saya dari dekat, tapi tolong jangan ambil kata-kata saya untuk itu dan cari sendiri.
Sanjeev
2
@ AutomatedMike Saya pikir menggambarkan Java sebagai "pass-by-reference" juga menyesatkan, referensi mereka tidak lebih dari pointer. Karena Sanjeev menulis nilai, referensi dan penunjuk adalah istilah buku teks yang memiliki maknanya sendiri terlepas dari apa yang digunakan pembuat konten Java.
Jędrzej Dudkiewicz
1
@ ArtanisZeratul intinya halus, tetapi tidak rumit. Silakan lihat kartun yang saya posting. Saya merasa itu cukup sederhana untuk diikuti. Bahwa Java Pass-by-value bukan hanya sebuah opini. Benar berdasarkan definisi pass-by-value dari buku teks. Juga, "orang-orang yang selalu mengatakan itu lewat nilai" termasuk ilmuwan komputer seperti James Gosling, pencipta Jawa. Silakan lihat kutipan dari bukunya di bawah "Edit 2" di posting saya.
Sanjeev
1
Apa jawaban yang bagus, "lulus dengan referensi" tidak ada di java (dan bahasa lain seperti JS).
folder baru
56

Sejauh yang saya tahu, Java hanya tahu panggilan berdasarkan nilai. Ini berarti untuk tipe data primitif Anda akan bekerja dengan salinan dan untuk objek Anda akan bekerja dengan salinan referensi ke objek. Namun saya pikir ada beberapa jebakan; misalnya, ini tidak akan berfungsi:

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

Ini akan mengisi Hello World dan bukan World Hello karena dalam fungsi swap Anda menggunakan copys yang tidak berdampak pada referensi di main. Tetapi jika objek Anda tidak berubah, Anda dapat mengubahnya misalnya:

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

Ini akan mengisi Hello World di baris perintah. Jika Anda mengubah StringBuffer menjadi String, itu hanya akan menghasilkan Halo karena String tidak dapat diubah. Sebagai contoh:

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

Namun Anda dapat membuat pembungkus untuk String seperti ini yang akan membuatnya dapat menggunakannya dengan Strings:

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

sunting: saya percaya ini juga alasan untuk menggunakan StringBuffer ketika datang untuk "menambahkan" dua Strings karena Anda dapat memodifikasi objek asli yang Anda tidak dapat dengan objek abadi seperti String.

kukuda
sumber
+1 untuk tes swap - mungkin cara yang paling mudah dan dapat diterima untuk membedakan antara melewati referensi dan melewati referensi berdasarkan nilai . Jika Anda dapat dengan mudah menulis fungsi swap(a, b)yang (1) bertukar adan bdari POV pemanggil, (2) adalah tipe-agnostik sejauh memungkinkan pengetikan statis (artinya menggunakannya dengan jenis lain tidak memerlukan apa pun selain mengubah jenis yang dinyatakan adan b) , dan (3) tidak mengharuskan penelepon untuk secara eksplisit melewati pointer atau nama, maka bahasa mendukung lewat referensi.
cHao
"..untuk tipe data primitif Anda akan bekerja dengan salinan dan untuk objek Anda akan bekerja dengan salinan referensi ke objek" - ditulis dengan sempurna!
Raúl
55

Tidak, ini bukan referensi.

Java dinyatakan bernilai menurut Spesifikasi Bahasa Jawa:

Ketika metode atau konstruktor dipanggil (§15.12), nilai-nilai dari ekspresi argumen aktual menginisialisasi variabel parameter yang baru dibuat , masing-masing dari tipe yang dideklarasikan, sebelum eksekusi badan metode atau konstruktor. Identifier yang muncul di DeclaratorId dapat digunakan sebagai nama sederhana di tubuh metode atau konstruktor untuk merujuk ke parameter formal .

rorrohprog
sumber
52

Biarkan saya mencoba menjelaskan pengertian saya dengan bantuan empat contoh. Java adalah pass-by-value, dan bukan pass-by-reference

/ **

Lewati Nilai

Di Jawa, semua parameter dilewatkan oleh nilai, yaitu menetapkan argumen metode tidak terlihat oleh pemanggil.

* /

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

Hasil

output : output
value : Nikhil
valueflag : false

Contoh 2:

/ ** * * Nilai Pass By * * /

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

Hasil

output : output
value : Nikhil
valueflag : false

Contoh 3:

/ ** 'Nilai Lulus Ini memiliki perasaan' Lulus Referensi '

Beberapa orang mengatakan tipe primitif dan 'String' adalah 'pass by value' dan objek adalah 'pass by reference'.

Tetapi dari contoh ini, kita dapat memahami bahwa itu hanya lewat nilai, mengingat bahwa di sini kita meneruskan referensi sebagai nilai. yaitu: referensi dilewatkan oleh nilai. Itu sebabnya mampu berubah dan tetap berlaku setelah lingkup lokal. Tetapi kami tidak dapat mengubah referensi aktual di luar lingkup asli. apa artinya itu ditunjukkan oleh contoh PassByValueObjectCase2 berikutnya.

* /

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

Hasil

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

Contoh 4:

/ **

Selain apa yang disebutkan dalam Example3 (PassByValueObjectCase1.java), kami tidak dapat mengubah referensi aktual di luar ruang lingkup asli. "

Catatan: Saya tidak menempelkan kode untuk private class Student. Definisi kelas untuk Studentsama dengan 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";
    }

}

Hasil

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

Anda tidak pernah dapat melewati referensi di Java, dan salah satu cara yang jelas adalah ketika Anda ingin mengembalikan lebih dari satu nilai dari pemanggilan metode. Pertimbangkan sedikit kode berikut dalam 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;
}

Terkadang Anda ingin menggunakan pola yang sama di Jawa, tetapi Anda tidak bisa; setidaknya tidak secara langsung. Sebaliknya Anda bisa melakukan sesuatu seperti ini:

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

Seperti yang dijelaskan dalam jawaban sebelumnya, di Jawa Anda memberikan pointer ke array sebagai nilai getValues. Itu sudah cukup, karena metode ini kemudian memodifikasi elemen array, dan dengan konvensi Anda mengharapkan elemen 0 mengandung nilai kembali. Jelas Anda bisa melakukan ini dengan cara lain, seperti menyusun kode Anda jadi ini tidak perlu, atau membangun kelas yang bisa berisi nilai balik atau membiarkannya diatur. Tetapi pola sederhana yang tersedia untuk Anda di C ++ di atas tidak tersedia di Jawa.

Jared Oberhaus
sumber
48

Saya pikir saya akan berkontribusi jawaban ini untuk menambahkan lebih detail dari Spesifikasi.

Pertama, Apa perbedaan antara passing dengan referensi vs passing dengan nilai?

Melewati dengan referensi berarti parameter fungsi yang dipanggil akan sama dengan argumen yang dilewati oleh penelepon (bukan nilai, tetapi identitas - variabel itu sendiri).

Lewat nilai berarti parameter fungsi yang dipanggil akan menjadi salinan dari argumen yang dilewati penelepon.

Atau dari wikipedia, dengan subjek pass-by-reference

Dalam evaluasi call-by-reference (juga disebut sebagai pass-by-reference), suatu fungsi menerima referensi implisit ke variabel yang digunakan sebagai argumen, alih-alih salinan nilainya. Ini biasanya berarti bahwa fungsi tersebut dapat memodifikasi (yaitu menetapkan) variabel yang digunakan sebagai argumen — sesuatu yang akan dilihat oleh pemanggilnya.

Dan tentang masalah pass-by-value

Dalam call-by-value, ekspresi argumen dievaluasi, dan nilai yang dihasilkan terikat ke variabel yang sesuai dalam fungsi [...]. Jika fungsi atau prosedur dapat menetapkan nilai ke parameternya, hanya salinan lokalnya yang diberikan [...].

Kedua, kita perlu tahu apa yang digunakan Java dalam doa metodenya. The Java Language Specification negara

Ketika metode atau konstruktor dipanggil (§15.12), nilai-nilai dari ekspresi argumen aktual menginisialisasi variabel parameter yang baru dibuat , masing-masing dari tipe yang dideklarasikan, sebelum eksekusi badan metode atau konstruktor.

Jadi itu memberikan (atau mengikat) nilai argumen ke variabel parameter yang sesuai.

Apa nilai argumennya?

Mari kita pertimbangkan jenis referensi, Spesifikasi Java Virtual Machine menyatakan

Ada tiga jenis tipe referensi : tipe kelas, tipe array, dan tipe antarmuka. Nilai-nilai mereka adalah referensi untuk instance kelas yang dibuat secara dinamis, array, atau instance kelas atau array yang mengimplementasikan antarmuka, masing-masing.

The Java Language Specification juga menyatakan

Nilai referensi (seringkali hanya referensi) adalah penunjuk ke objek ini , dan referensi nol khusus, yang merujuk ke objek yang tidak ada.

Nilai argumen (dari beberapa jenis referensi) adalah penunjuk ke objek. Perhatikan bahwa variabel, doa metode dengan tipe pengembalian tipe referensi, dan ekspresi pembuatan instance ( new ...) semua menyelesaikan ke nilai tipe referensi.

Begitu

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

semua mengikat nilai referensi ke Stringinstance ke parameter metode yang baru dibuat param,. Inilah yang dijelaskan oleh definisi pass-by-value. Dengan demikian, Java adalah pass-by-value .

Fakta bahwa Anda dapat mengikuti referensi untuk memanggil metode atau mengakses bidang objek yang direferensikan sama sekali tidak relevan dengan percakapan. Definisi pass-by-reference adalah

Ini biasanya berarti bahwa fungsi tersebut dapat memodifikasi (yaitu menetapkan) variabel yang digunakan sebagai argumen — sesuatu yang akan dilihat oleh pemanggilnya.

Di Jawa, memodifikasi variabel berarti menugaskannya kembali. Di Jawa, jika Anda menugaskan kembali variabel dalam metode, itu akan luput dari perhatian ke pemanggil. Memodifikasi objek yang dirujuk oleh variabel adalah konsep yang sama sekali berbeda.


Nilai-nilai primitif juga didefinisikan dalam Spesifikasi Mesin Virtual Java, di sini . Nilai tipe adalah nilai integral atau floating point yang sesuai, dikodekan dengan tepat (8, 16, 32, 64, dll. Bit).

Sotirios Delimanolis
sumber
42

Di Jawa, hanya referensi yang diteruskan dan diberikan nilai:

Argumen Java semuanya diteruskan oleh nilai (referensi disalin saat digunakan oleh metode):

Dalam kasus tipe primitif, perilaku Java sederhana: Nilai disalin dalam contoh lain dari tipe primitif.

Dalam kasus Objects, ini adalah sama: Variabel objek adalah pointer (ember) yang hanya memegang alamat Obyek yang dibuat menggunakan kata kunci "baru", dan disalin seperti tipe primitif.

Perilaku dapat muncul berbeda dari tipe primitif: Karena variabel objek yang disalin berisi alamat yang sama (ke Objek yang sama). Objek konten / anggota mungkin masih dimodifikasi dalam metode dan akses kemudian luar, memberikan ilusi bahwa (yang mengandung) Object itu sendiri disahkan oleh referensi.

Objek "String" tampaknya menjadi contoh yang bagus untuk legenda urban yang mengatakan bahwa "Objek dilewatkan dengan referensi":

Akibatnya, menggunakan metode, Anda tidak akan pernah bisa, untuk memperbarui nilai String yang dilewatkan sebagai argumen:

Objek String, menampung karakter dengan array yang dinyatakan final yang tidak dapat dimodifikasi. Hanya alamat Objek yang dapat digantikan oleh yang lain menggunakan "baru". Menggunakan "baru" untuk memperbarui variabel, tidak akan membiarkan Objek diakses dari luar, karena variabel awalnya disahkan oleh nilai dan disalin.

user1767316
sumber
Jadi byRef dalam hal objek dan byVal dalam hal primitif?
Mox
@mox tolong baca: Objek tidak dilewatkan dengan referensi, ini adalah ledgend: String a = new String ("unchanged");
user1767316
1
@ Harun "Lulus dengan referensi" tidak berarti "memberikan nilai yang merupakan anggota dari tipe yang disebut 'referensi' di Jawa". Dua penggunaan "referensi" memiliki arti yang berbeda.
philipxy
2
Jawaban yang cukup membingungkan. Alasan mengapa Anda tidak dapat mengubah objek string dalam metode, yang dilewatkan oleh referensi, adalah bahwa objek String tidak dapat diubah oleh desain dan Anda tidak dapat melakukan hal-hal seperti strParam.setChar ( i, newValue ). Yang mengatakan, string, seperti hal lain, dilewatkan oleh nilai dan, karena String adalah tipe non-primitif, nilai itu adalah referensi ke hal yang dibuat dengan yang baru, dan Anda bisa memeriksanya dengan menggunakan String.intern () .
zakmck
1
Sebaliknya, Anda tidak dapat mengubah string melalui param = "string lain" (setara dengan String baru ("string lain")) karena nilai referensi param (yang sekarang menunjuk ke "string lain") tidak dapat kembali dari metode tubuh. Tapi itu berlaku untuk objek lain, bedanya adalah, ketika antarmuka kelas memungkinkan untuk itu, Anda dapat melakukan param.changeMe () dan objek tingkat atas yang dirujuk akan berubah karena param menunjuk padanya, meskipun nilai referensi param itu sendiri (lokasi yang dituju, dalam istilah C) tidak dapat muncul kembali dari metode.
zakmck
39

Perbedaannya, atau mungkin hanya dengan cara yang saya ingat seperti dulu berada di bawah kesan yang sama dengan poster aslinya adalah ini: Jawa selalu dinilai berdasarkan nilai. Semua objek (di Jawa, apa pun kecuali primitif) di Jawa adalah referensi. Referensi-referensi ini diteruskan oleh nilai.

shsteimer
sumber
2
Saya menemukan kalimat kedua hingga terakhir Anda sangat menyesatkan. Tidak benar bahwa "semua objek di Jawa adalah referensi". Ini hanya referensi ke objek-objek yang merupakan referensi.
Dawood ibn Kareem
37

Seperti yang banyak orang sebutkan sebelumnya, Java selalu dianggap sebagai nilai tambah

Berikut adalah contoh lain yang akan membantu Anda memahami perbedaannya ( contoh pertukaran klasik ):

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

Cetakan:

Sebelum: a = 2, b = 3
Setelah: a = 2, b = 3

Ini terjadi karena iA dan iB adalah variabel referensi lokal baru yang memiliki nilai yang sama dari referensi yang diteruskan (mereka masing-masing menunjuk ke a dan b). Jadi, mencoba mengubah referensi iA atau iB hanya akan berubah dalam cakupan lokal dan tidak di luar metode ini.

pek
sumber
32

Java hanya memiliki nilai by pass. Contoh yang sangat sederhana untuk memvalidasi ini.

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();
}
Gaurav
sumber
4
Ini adalah cara yang paling jelas dan paling sederhana untuk melihat bahwa Java melewati nilai. Nilai obj( null) diteruskan ke init, bukan referensi ke obj.
David Schwartz
31

Saya selalu menganggapnya sebagai "pass by copy". Ini adalah salinan nilai baik primitif atau referensi. Jika itu adalah primitif itu adalah salinan dari bit yang merupakan nilainya dan jika itu adalah Obyek itu adalah salinan referensi.

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

output dari java PassByCopy:

name = Maxx
name = Fido

Kelas pembungkus primitif dan String tidak dapat diubah sehingga contoh apa pun yang menggunakan tipe-tipe itu tidak akan berfungsi sama dengan tipe / objek lainnya.

SWD
sumber
5
"melewati copy" adalah apa yang pass-by-nilai berarti .
TJ Crowder
@TJCrowder. "Pass by copy" mungkin cara yang lebih tepat untuk mengekspresikan konsep yang sama untuk tujuan Java: karena secara semantik membuatnya sangat sulit untuk disisipkan dalam ide yang mirip dengan meneruskan dengan referensi, yang adalah apa yang Anda inginkan di Jawa.
mike rodent
@mikerodent - Maaf, saya tidak mengikuti itu. :-) "Pass-by-value" dan "pass-by-reference" adalah istilah seni yang tepat untuk konsep-konsep ini. Kita harus menggunakannya (memberikan definisi mereka ketika orang membutuhkannya) daripada membuat yang baru. Di atas saya katakan "pass by copy" artinya "pass-by-value", tetapi sebenarnya tidak jelas apa arti "pass by copy" - salinan apa? Nilai? (Misalnya, referensi objek.) Objek itu sendiri? Jadi saya berpegang pada ketentuan seni. :-)
TJ Crowder
28

Tidak seperti beberapa bahasa lain, Java tidak memungkinkan Anda untuk memilih antara pass-by-value dan pass-by-reference — semua argumen dilewati oleh nilai. Pemanggilan metode dapat meneruskan dua jenis nilai ke metode — salinan nilai primitif (mis., Nilai int dan dobel) dan salinan referensi ke objek.

Ketika metode memodifikasi parameter tipe primitif, perubahan pada parameter tidak berpengaruh pada nilai argumen asli dalam metode pemanggilan.

Ketika datang ke objek, objek itu sendiri tidak dapat diteruskan ke metode. Jadi kami melewati referensi (alamat) objek. Kami dapat memanipulasi objek asli menggunakan referensi ini.

Bagaimana Java membuat dan menyimpan objek: Ketika kami membuat objek, kami menyimpan alamat objek dalam variabel referensi. Mari kita menganalisis pernyataan berikut.

Account account1 = new Account();

"Akun akun1" adalah jenis dan nama variabel referensi, "=" adalah operator penugasan, "baru" meminta jumlah ruang yang diperlukan dari sistem. Konstruktor di sebelah kanan kata kunci baru yang membuat objek disebut secara implisit oleh kata kunci baru. Alamat objek yang dibuat (hasil nilai kanan, yang merupakan ekspresi yang disebut "ekspresi instance kelas") ditugaskan ke nilai kiri (yang merupakan variabel referensi dengan nama dan tipe yang ditentukan) menggunakan operator yang ditugaskan.

Meskipun referensi objek dilewatkan oleh nilai, suatu metode masih dapat berinteraksi dengan objek yang direferensikan dengan memanggil metode publik menggunakan salinan referensi objek. Karena referensi yang disimpan dalam parameter adalah salinan referensi yang dilewatkan sebagai argumen, parameter dalam metode yang dipanggil dan argumen dalam metode panggilan merujuk ke objek yang sama dalam memori.

Melewati referensi ke array, bukan objek array itu sendiri, masuk akal karena alasan kinerja. Karena semua yang ada di Jawa diteruskan oleh nilai, jika objek array dilewatkan, salinan dari setiap elemen akan diteruskan. Untuk array besar, ini akan menghabiskan waktu dan menghabiskan banyak penyimpanan untuk salinan elemen.

Pada gambar di bawah ini Anda dapat melihat kami memiliki dua variabel referensi (Ini disebut pointer di C / C ++, dan saya pikir istilah itu membuatnya lebih mudah untuk memahami fitur ini.) Dalam metode utama. Variabel primitif dan referensi disimpan dalam memori tumpukan (sisi kiri pada gambar di bawah). variabel referensi array1 dan array2 "titik" (seperti yang disebut programmer C / C ++) atau masing-masing referensi ke a dan b, yang merupakan objek (nilai yang dipegang variabel referensi ini adalah alamat objek) dalam memori tumpukan (sisi kanan pada gambar di bawah) .

Lewati dengan nilai contoh 1

Jika kita meneruskan nilai variabel referensi array1 sebagai argumen ke metode reverseArray, variabel referensi dibuat dalam metode dan variabel referensi mulai menunjuk ke array yang sama (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);
    }
}

Lewati dengan contoh nilai 2

Jadi, jika kita katakan

array1[0] = 5;

dalam metode reverseArray, itu akan membuat perubahan dalam array a.

Kami memiliki variabel referensi lain dalam metode reverseArray (array2) yang menunjuk ke sebuah array c. Jika kita mengatakannya

array1 = array2;

dalam metode reverseArray, maka array variabel referensi1 dalam metode reverseArray akan berhenti menunjuk ke array a dan mulai menunjuk ke array c (garis putus-putus pada gambar kedua).

Jika kita mengembalikan nilai array variabel referensi2 sebagai nilai balik metode reverseArray dan menetapkan nilai ini ke variabel referensi array1 dalam metode utama, array1 pada main akan mulai menunjuk ke array c.

Jadi mari kita tuliskan semua hal yang telah kita lakukan sekaligus sekarang.

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 */
    }
}

masukkan deskripsi gambar di sini

Dan sekarang metode reverseArray selesai, variabel referensi (array1 dan array2) hilang. Yang berarti kita sekarang hanya memiliki dua variabel referensi dalam metode utama array1 dan array2 yang masing-masing mengarah ke array c dan b. Tidak ada variabel referensi yang menunjuk ke objek (array) a. Jadi memenuhi syarat untuk pengumpulan sampah.

Anda juga bisa menetapkan nilai array2 di main ke array1. array1 akan mulai menunjuk ke b.

Michael
sumber
27

Saya telah membuat utas yang ditujukan untuk pertanyaan semacam ini untuk bahasa pemrograman apa pun di sini .

Java juga disebutkan . Berikut ini ringkasan singkatnya:

  • Java memberikan parameter berdasarkan nilai
  • "by value" adalah satu-satunya cara di java untuk mengirimkan parameter ke metode
  • menggunakan metode dari objek yang diberikan sebagai parameter akan mengubah objek sebagai titik referensi ke objek asli. (jika metode itu sendiri mengubah beberapa nilai)
sven
sumber
27

Singkatnya, objek Java memiliki beberapa properti yang sangat aneh.

Secara umum, Java memiliki tipe primitif ( int, bool, char, double, dll) yang disahkan langsung oleh nilai. Kemudian Java memiliki objek (semua yang berasal darijava.lang.Object ). Objek sebenarnya selalu ditangani melalui referensi (referensi menjadi pointer yang tidak dapat Anda sentuh). Itu berarti bahwa pada dasarnya, objek dilewatkan dengan referensi, karena referensi biasanya tidak menarik. Namun itu berarti bahwa Anda tidak dapat mengubah objek yang ditunjuk sebagai referensi itu sendiri dilewatkan oleh nilai.

Apakah ini terdengar aneh dan membingungkan? Mari kita pertimbangkan bagaimana C mengimplementasikan pass by reference dan pass by value. Dalam C, konvensi standar adalah nilai lewat. void foo(int x)melewati int dengan nilai. void foo(int *x)adalah fungsi yang tidak ingin int a, tetapi pointer ke int: foo(&a). Orang akan menggunakan ini dengan &operator untuk mengirimkan alamat variabel.

Bawa ini ke C ++, dan kami punya referensi. Referensi pada dasarnya (dalam konteks ini) gula sintaksis yang menyembunyikan bagian penunjuk persamaan: void foo(int &x)dipanggil oleh foo(a), di mana kompiler itu sendiri tahu bahwa itu adalah referensi dan alamat non-referensi aharus dilewatkan. Di Jawa, semua variabel yang merujuk ke objek sebenarnya dari tipe referensi, sebagai akibatnya memaksa panggilan dengan referensi untuk sebagian besar maksud dan tujuan tanpa kontrol berbutir halus (dan kompleksitas) yang diberikan oleh, misalnya, C ++.

Paul de Vrieze
sumber
Saya pikir ini sangat dekat dengan pemahaman saya tentang objek Java dan referensi. Objek di Java diteruskan ke metode dengan salinan referensi (atau alias).
MaxZoom