Kapan Anda ingin dua referensi ke objek yang sama?

20

Di Jawa secara khusus, tetapi kemungkinan dalam bahasa lain juga: kapan akan berguna untuk memiliki dua referensi ke objek yang sama?

Contoh:

Dog a = new Dog();
Dob b = a;

Apakah ada situasi di mana ini akan bermanfaat? Mengapa ini menjadi solusi yang lebih disukai untuk digunakan akapan pun Anda ingin berinteraksi dengan objek yang diwakili a?

Bassinator
sumber
Ini adalah esensi di balik pola Flyweight .
@MichaelT Bisakah Anda jelaskan?
Bassinator
7
Jika adan b selalu merujuk hal yang sama Dog, maka tidak ada gunanya. Jika mereka kadang - kadang mungkin, maka itu sangat berguna.
Gabe

Jawaban:

45

Contohnya adalah ketika Anda ingin memiliki objek yang sama dalam dua daftar terpisah :

Dog myDog = new Dog();
List dogsWithRabies = new ArrayList();
List dogsThatCanPlayPiano = new ArrayList();

dogsWithRabies.add(myDog);
dogsThatCanPlayPiano.add(myDog);
// Now each List has a reference to the same dog

Penggunaan lain adalah ketika Anda memiliki objek yang sama memainkan beberapa peran :

Person p = new Person("Bruce Wayne");
Person batman = p;
Person ceoOfWayneIndustries = p;
Tulains Córdova
sumber
4
Saya minta maaf tapi saya percaya bahwa untuk contoh Bruce Wayne Anda ada cacat desain, posisi batman dan ceo harus menjadi peran untuk orang Anda.
Silviu Burcea
44
-1 untuk mengungkapkan identitas rahasia Batman.
Ampt
2
@ Silviu Burcea - Saya sangat setuju bahwa contoh Bruce Wayne bukan contoh yang baik. Di satu sisi, jika edit teks global mengubah nama 'ceoOfWayneIndustries' dan 'batman' menjadi 'p' (dengan asumsi tidak ada bentrokan nama atau perubahan ruang lingkup), dan semantik program berubah, maka semantiknya berubah, maka itu adalah sesuatu yang rusak. Objek yang direferensikan mewakili arti sebenarnya, bukan nama variabel dalam program. Untuk memiliki semantik yang berbeda, itu adalah objek yang berbeda, atau direferensikan oleh sesuatu dengan perilaku lebih dari referensi (yang harus transparan), dan bukan contoh 2+ referensi.
gbulmer
2
Meskipun contoh Bruce Wayne yang ditulis mungkin tidak berhasil, saya percaya niat yang dinyatakan itu benar. Mungkin contoh yang lebih dekat mungkin Persona batman = new Persona("Batman"); Persona bruce = new Persona("Bruce Wayne"); Persona currentPersona = batman;- di mana Anda memiliki beberapa nilai yang mungkin (atau daftar nilai yang tersedia) dan referensi ke yang saat ini aktif / dipilih.
Dan Puzey
1
@bulb: Saya berpendapat bahwa Anda tidak dapat memiliki referensi ke dua objek; referensi currentPersonamenunjuk ke satu objek atau yang lain, tetapi tidak pernah keduanya. Secara khusus, mungkin dengan mudah mungkin bahwa currentPersonaini tidak pernah diatur untuk bruce, dalam hal itu jelas tidak referensi ke dua objek. Saya akan mengatakan itu, dalam contoh saya, keduanya batmandan currentPersonamerupakan referensi untuk contoh yang sama, tetapi melayani makna semantik yang berbeda dalam program.
Dan Puzey
16

Itu sebenarnya pertanyaan yang sangat mengejutkan! Pengalaman dari C ++ modern (dan bahasa yang mengambil dari C ++ modern, seperti Rust) menunjukkan bahwa sangat sering, Anda tidak menginginkan itu! Untuk sebagian besar data, Anda ingin referensi tunggal atau unik ("memiliki"). Ide ini juga merupakan satu alasan utama untuk sistem tipe linear .

Namun, meskipun demikian Anda biasanya menginginkan beberapa referensi "pinjaman" berumur pendek yang digunakan untuk mengakses memori secara singkat tetapi tidak bertahan untuk sebagian kecil dari waktu data itu ada. Paling umum ketika Anda melewatkan objek sebagai argumen ke fungsi yang berbeda (Parameter juga variabel!):

void encounter(Dog a) {
  hissAt(a);
}

void hissAt(Dog b) {
  // ...
}

Kasus yang kurang umum ketika Anda menggunakan salah satu dari dua objek tergantung pada suatu kondisi, pada dasarnya melakukan hal yang sama terlepas dari apa yang Anda pilih:

Dog a, b;
Dog goodBoy = whoseAGoodBoy ? a : b;
feed(goodBoy);
walk(goodBoy);
pet(goodBoy);

Kembali ke penggunaan yang lebih umum, tetapi meninggalkan variabel lokal, kita beralih ke bidang: Misalnya, widget dalam kerangka GUI sering memiliki widget induk, sehingga kerangka besar Anda yang berisi sepuluh tombol akan memiliki setidaknya sepuluh referensi yang menunjuk padanya (ditambah beberapa lagi dari yang tua dan mungkin dari pendengar acara dan sebagainya). Setiap jenis objek grafik, dan beberapa jenis pohon objek (yang memiliki referensi induk / saudara kandung), memiliki beberapa objek merujuk ke masing-masing objek yang sama. Dan sebenarnya setiap set data sebenarnya adalah grafik ;-)


sumber
3
"Kasus kurang umum" cukup umum: Anda memiliki objek yang disimpan dalam daftar atau peta, mengambil yang Anda inginkan, dan melakukan operasi yang diperlukan. Anda tidak menghapus referensi mereka dari peta atau daftar.
SJuan76
1
@ SJuan76 "Kasus kurang umum" semata-mata tentang mengambil dari variabel lokal, apa pun yang melibatkan struktur data berada di bawah titik terakhir.
Apakah ini ideal hanya satu referensi karena manajemen memori penghitungan referensi? Jika demikian, perlu disebutkan bahwa motivasi tersebut tidak relevan dengan bahasa lain dengan pemulung yang berbeda (mis. C #, Java, Python)
MarkJ
@ MarkJ jenis Linear bukan hanya ideal, banyak kode yang ada tanpa sadar sesuai dengan itu karena itu adalah hal yang benar secara semantik untuk beberapa kasus. Itu memang memiliki potensi keuntungan kinerja (tetapi tidak untuk penghitungan referensi atau pelacakan GC, itu hanya membantu ketika Anda menggunakan strategi yang berbeda yang benar-benar mengeksploitasi pengetahuan itu untuk menghilangkan kedua penghitungan ulang dan penelusuran). Lebih menarik adalah aplikasinya untuk manajemen sumber daya yang sederhana dan deterministik. Pikirkan RAII dan C ++ 11 memindahkan semantik, tetapi lebih baik (berlaku lebih sering dan kesalahan ditangkap oleh kompiler).
6

Variabel sementara: pertimbangkan kodesemu berikut.

Object getMaximum(Collection objects) {
  Object max = null;
  for (Object candidate IN objects) {
    if ((max is null) OR (candidate > max)) {
      max = candidate;
    }
  }
  return max;
}

Variabel maxdan candidatemungkin menunjuk ke objek yang sama, tetapi penugasan variabel berubah menggunakan aturan yang berbeda dan pada waktu yang berbeda.


sumber
3

Untuk melengkapi jawaban lain, Anda mungkin juga ingin melintasi struktur data secara berbeda, mulai dari tempat yang sama. Misalnya, jika Anda memiliki BinaryTree a = new BinaryTree(...); BinaryTree b = a, Anda dapat melintasi jalan paling kiri pohon dengan adan jalan paling kanan dengannya b, menggunakan sesuatu seperti:

while (!a.equals(null) && !b.equals(null)) {
    a = a.left();
    b = b.right();
}

Sudah lama sejak saya menulis Java, sehingga kode itu mungkin tidak benar atau masuk akal. Ambillah lebih sebagai pseudocode.

Mike
sumber
3

Metode ini sangat bagus ketika Anda memiliki beberapa objek yang semuanya memanggil kembali ke objek lain yang dapat digunakan secara tidak kontekstual.

Misalnya, jika Anda memiliki antarmuka tab Anda mungkin memiliki Tab1, Tab2, dan Tab3. Anda juga mungkin ingin dapat menggunakan variabel umum terlepas dari tab mana pengguna aktif untuk menyederhanakan kode Anda dan mengurangi harus mencari tahu dengan cepat di mana tab pengguna Anda berada.

Tab Tab1 = new Tab();
Tab Tab2 = new Tab();
Tab Tab3 = new Tab();
Tab CurrentTab = new Tab();

Lalu, di masing-masing tab bernomor diKlik, Anda bisa mengubah CurrentTab untuk referensi Tab itu.

CurrentTab = Tab3;

Sekarang dalam kode Anda, Anda dapat memanggil "CurrentTab" dengan bebas dari hukuman tanpa perlu tahu di mana Anda sebenarnya berada. Anda juga dapat memperbarui properti CurrentTab dan secara otomatis akan mengalir ke Tab yang direferensikan.

JMD
sumber
3

Ada banyak skenario di mana b harus ada referensi ke " a" yang tidak diketahui agar bermanfaat. Khususnya:

  • Kapan saja Anda tidak tahu apa yang bmenunjuk pada saat kompilasi.
  • Kapan saja Anda perlu mengulangi koleksi, apakah diketahui pada waktu kompilasi atau tidak
  • Kapan saja Anda memiliki ruang lingkup terbatas

Sebagai contoh:

Parameter

public void DoSomething(Thing &t) {
}

t adalah referensi ke variabel dari lingkup luar.

Mengembalikan nilai dan nilai kondisional lainnya

Thing a = Thing.Get("a");
Thing b = Thing.Get("b");
Thing biggerThing = Thing.max(a, b);
Thing z = a.IsMoreZThan(b) ? a : b;

biggerThingdan zmasing-masing referensi untuk salah satu aatau b. Kami tidak tahu yang mana pada saat kompilasi.

Lambdas dan nilai kembalinya mereka

Thing t = someCollection.FirstOrDefault(x => x.whatever > 123);

xadalah parameter (contoh 1 di atas), dan tmerupakan nilai balik (contoh 2 di atas)

Koleksi

indexByName.add(t.name, t);
process(indexByName["some name"]);

index["some name"]adalah, sebagian besar, tampak lebih canggih b. Ini adalah alias untuk objek yang dibuat dan dimasukkan ke dalam koleksi.

Loop

foreach (Thing t in things) {
 /* `t` is a reference to a thing in a collection */
}

t adalah referensi ke item yang dikembalikan (contoh 2) oleh iterator (contoh sebelumnya).

svidgen
sumber
Teladan Anda sulit diikuti. Jangan salah paham, saya berhasil melewati mereka, tetapi saya harus berusaha. Di masa mendatang, saya sarankan memisahkan contoh kode Anda menjadi blok individual (dengan beberapa catatan khusus menjelaskan masing-masing), tidak menempatkan beberapa contoh yang tidak terkait dalam satu blok.
Bassinator
1
@HCBPshenanigans Saya tidak berharap Anda mengubah jawaban yang dipilih; tetapi, saya telah memperbarui tambang untuk semoga membantu keterbacaan dan mengisi beberapa kasus penggunaan yang hilang dari jawaban yang dipilih.
svidgen
2

Ini adalah poin penting, tapi IMHO patut dipahami.

Semua bahasa OO selalu membuat salinan referensi, dan tidak pernah menyalin objek 'tidak terlihat'. Akan jauh lebih sulit untuk menulis program jika bahasa OO bekerja dengan cara lain. Misalnya, fungsi, dan metode, tidak pernah dapat memperbarui objek. Java, dan sebagian besar bahasa OO hampir tidak mungkin digunakan tanpa kompleksitas tambahan yang signifikan.

Objek dalam suatu program seharusnya memiliki arti. Misalnya itu mewakili sesuatu yang spesifik di dunia fisik nyata. Biasanya masuk akal jika memiliki banyak referensi untuk hal yang sama. Misalnya, alamat rumah saya dapat diberikan kepada banyak orang dan organisasi, dan alamat itu selalu merujuk ke lokasi fisik yang sama. Jadi poin pertama adalah, obyek sering mewakili sesuatu yang spesifik, nyata, atau konkret; sehingga dapat memiliki banyak referensi untuk hal yang sama sangat berguna. Kalau tidak, akan lebih sulit untuk menulis program.

Setiap kali Anda meneruskan asebagai argumen / parameter ke fungsi lain misalnya memanggil
foo(Dog aDoggy);
atau menerapkan metode a, kode program yang mendasari membuat salinan referensi, untuk menghasilkan referensi kedua ke objek yang sama.

Lebih lanjut, jika kode dengan referensi yang disalin berada di utas yang berbeda, maka keduanya dapat digunakan secara bersamaan untuk mengakses objek yang sama.

Jadi dalam sebagian besar program yang bermanfaat, akan ada banyak referensi ke objek yang sama, karena itu adalah semantik dari sebagian besar bahasa pemrograman OO.

Sekarang, jika kita memikirkannya, karena lewat referensi adalah satu - satunya mekanisme yang tersedia dalam banyak bahasa OO (C ++ mendukung keduanya), kita mungkin berharap itu menjadi perilaku default 'benar' .

IMHO, menggunakan referensi adalah default yang tepat , karena beberapa alasan:

  1. Ini menjamin bahwa nilai suatu objek yang digunakan di dua tempat berbeda adalah sama. Bayangkan menempatkan sebuah objek ke dalam dua struktur data yang berbeda (array, daftar, dll.), Dan melakukan beberapa operasi pada objek yang mengubahnya. Itu bisa menjadi mimpi buruk untuk debug. Lebih penting lagi, itu adalah objek yang sama di kedua struktur data, atau program memiliki bug.
  2. Anda dapat dengan senang hati mengubah kode menjadi beberapa fungsi, atau menggabungkan kode dari beberapa fungsi menjadi satu, dan semantiknya tidak berubah. Jika bahasa tidak memberikan semantik referensi, itu akan menjadi lebih kompleks untuk memodifikasi kode.

Ada juga argumen efisiensi; membuat salinan seluruh objek kurang efisien daripada menyalin referensi. Namun, saya pikir itu melenceng. Berbagai referensi ke objek yang sama lebih masuk akal, dan lebih mudah digunakan, karena cocok dengan semantik dunia fisik nyata.

Jadi, IMHO, biasanya masuk akal untuk memiliki banyak referensi ke objek yang sama. Dalam kasus yang tidak biasa di mana itu tidak masuk akal dalam konteks suatu algoritma, sebagian besar bahasa menyediakan kemampuan untuk membuat 'klon' atau salinan dalam. Namun itu bukan default.

Saya pikir orang-orang yang berpendapat bahwa ini seharusnya bukan default menggunakan bahasa yang tidak menyediakan pengumpulan sampah otomatis. Misalnya, C ++ kuno. Masalahnya adalah bahwa mereka perlu menemukan cara untuk mengumpulkan benda-benda 'mati' dan tidak mengklaim kembali benda-benda yang mungkin masih diperlukan; memiliki banyak referensi ke objek yang sama menjadikannya sulit.

Saya pikir, jika C ++ memiliki pengumpulan sampah yang cukup murah, sehingga semua objek yang direferensikan adalah sampah yang dikumpulkan, maka sebagian besar keberatan hilang. Masih ada beberapa kasus di mana semantik referensi tidak dibutuhkan. Namun, dalam pengalaman saya, orang-orang yang dapat mengidentifikasi situasi-situasi itu juga biasanya mampu memilih semantik yang sesuai pula.

Saya percaya ada beberapa bukti bahwa sejumlah besar kode dalam program C ++ ada untuk menangani atau mengurangi pengumpulan sampah. Namun, menulis, dan memelihara kode 'infrastruktur' semacam itu menambah biaya; itu ada untuk membuat bahasa lebih mudah digunakan, atau lebih kuat. Jadi, misalnya bahasa Go dirancang dengan fokus untuk memulihkan beberapa kelemahan C ++, dan tidak memiliki pilihan selain pengumpulan sampah.

Ini tentu saja tidak relevan dalam konteks Jawa. Itu juga dirancang agar mudah digunakan, dan begitu pula pengumpulan sampah. Oleh karena itu memiliki banyak referensi adalah semantik default, dan relatif aman dalam arti bahwa objek tidak direklamasi sementara ada referensi ke sana. Tentu saja mereka mungkin dipegang oleh struktur data karena program tidak benar merapikan ketika sudah benar-benar selesai dengan objek.

Jadi, berputar kembali ke pertanyaan Anda (dengan sedikit generalisasi), kapan Anda ingin lebih dari satu referensi ke objek yang sama? Cukup banyak dalam setiap situasi yang dapat saya pikirkan. Mereka adalah semantik default dari sebagian besar mekanisme parameter lewat bahasa. Saya menyarankan itu karena semantik default untuk menangani objek yang ada di dunia nyata cukup banyak dengan referensi ('karena objek yang sebenarnya ada di luar sana).

Semantik lainnya akan lebih sulit ditangani.

Dog a = new Dog("rover");  // initialise with name 
DogList dl = new DogList()
dl.add(a)
...
a.setOwner("Mr Been")

Saya menyarankan bahwa "bajak" di dlharus yang dipengaruhi oleh setOwneratau program menjadi sulit untuk menulis, memahami, men-debug atau memodifikasi. Saya pikir sebagian besar programmer akan bingung atau kecewa sebaliknya.

kemudian, anjing itu dijual:

soldDog = dl.lookupOwner("rover", "Mr Been")
soldDog.setOwner("Mr Mcgoo")

Jenis pemrosesan ini biasa dan normal. Jadi referensi semantik adalah default karena biasanya paling masuk akal.

Ringkasan: Selalu masuk akal untuk memiliki banyak referensi ke objek yang sama.

gelandangan
sumber
Poin yang bagus, tapi ini lebih cocok sebagai komentar
Bassinator
@HCBPshenanigans - Saya pikir intinya mungkin terlalu singkat, dan meninggalkan terlalu banyak yang tidak terucapkan. Jadi saya telah mengembangkan alasannya. Saya pikir ini adalah jawaban 'meta' untuk pertanyaan Anda. Ringkasan, banyak referensi ke objek yang sama sangat penting untuk membuat program mudah ditulis karena banyak objek dalam program mewakili contoh spesifik atau unik dari hal-hal di dunia nyata.
gbulmer
1

Tentu saja, satu skenario lain di mana Anda mungkin berakhir dengan:

Dog a = new Dog();
Dog b = a;

adalah ketika Anda memelihara kode dan bdigunakan untuk menjadi anjing yang berbeda, atau kelas yang berbeda, tetapi sekarang dilayani oleh a.

Secara umum, dalam jangka menengah, Anda harus mengolah kembali semua kode untuk merujuk alangsung, tetapi itu mungkin tidak langsung terjadi.

Mark Hurd
sumber
1

Anda ingin ini kapan saja program Anda memiliki peluang menarik entitas ke dalam memori di lebih dari satu tempat, mungkin karena berbagai komponen menggunakannya.

Identity Maps menyediakan penyimpanan entitas lokal yang pasti sehingga kami dapat menghindari memiliki dua atau lebih representasi terpisah. Saat kami merepresentasikan objek yang sama dua kali, klien kami berisiko menyebabkan masalah konkurensi jika satu referensi objek bertahan keadaannya berubah sebelum instance lain lainnya melakukannya. Idenya adalah kami ingin memastikan bahwa klien kami selalu memberikan referensi definitif ke entitas / objek kami.

Mario T. Lanza
sumber
0

Saya menggunakan ini saat menulis pemecah Sudoku. Ketika saya tahu jumlah sel saat memproses baris, saya ingin kolom yang berisi untuk mengetahui nomor sel juga saat memproses kolom. Jadi baik kolom dan baris adalah array objek Cell yang tumpang tindih. Persis seperti yang ditunjukkan jawaban yang diterima.

winkbrace
sumber
-2

Dalam aplikasi web, Pemetaan Objek Relasional dapat menggunakan pemuatan malas sehingga semua referensi ke objek basis data yang sama (setidaknya dalam utas yang sama) menunjuk ke hal yang sama.

Misalnya, jika Anda memiliki dua tabel:

Anjing:

  • id | owner_id | nama
  • 1 | 1 | Bark Kent

Pemilik:

  • id | nama
  • 1 | saya
  • 2 | kamu

Ada beberapa pendekatan yang dapat dilakukan ORM Anda jika panggilan berikut dilakukan:

dog = Dog.find(1)  // fetch1
owner = Owner.find(1) // fetch2
superdog = owner.dogs.first() // fetch3
superdog.name = "Superdog"
superdog.save! // write1
owner = dog.owner // fetch4
owner.name = "Mark"
owner.save! // write2
dog.owner = Owner.find(2)
dog.save! // write3

Dalam strategi naif, semua panggilan ke model dan referensi terkait mengambil objek yang terpisah. Dog.find(), Owner.find(), owner.dogs, Dan dog.ownerhasilnya dalam database hit pertama kali sekitar, setelah itu mereka akan disimpan ke memori. Dan sebagainya:

  • database diambil setidaknya dari 4 kali
  • dog.owner tidak sama dengan superdog.owner (diambil secara terpisah)
  • dog.name tidak sama dengan superdog.name
  • dog dan superdog berusaha untuk menulis pada baris yang sama dan akan menimpa hasil masing-masing: write3 akan membatalkan perubahan nama pada write1.

Tanpa referensi, Anda memiliki lebih banyak pengambilan, menggunakan lebih banyak memori, dan memperkenalkan kemungkinan menimpa pembaruan sebelumnya.

Misalkan ORM Anda tahu bahwa semua referensi ke baris 1 dari tabel dogs harus mengarah ke hal yang sama. Kemudian:

  • fetch4 dapat dihilangkan karena ada objek dalam memori yang sesuai dengan Owner.find (1). fetch3 masih akan menghasilkan setidaknya pemindaian indeks, karena mungkin ada anjing lain yang dimiliki oleh pemilik, tetapi tidak akan memicu pencarian baris.
  • anjing dan anjing super menunjuk ke objek yang sama.
  • dog.name dan superdog.name menunjuk ke objek yang sama.
  • dog.owner dan superdog.owner menunjuk ke objek yang sama.
  • write3 tidak menimpa perubahan pada write1.

Dengan kata lain, menggunakan referensi membantu mengkodifikasi prinsip satu titik kebenaran (setidaknya dalam utas itu) menjadi baris dalam database.

madumlao
sumber