Cara Membuat Salinan Objek Dalam di Jawa

1. Pengenalan

Ketika kita ingin menyalin objek di Jawa, ada dua kemungkinan yang perlu kita pertimbangkan - salinan dangkal dan salinan mendalam.

Salinan cetek adalah pendekatan ketika kita hanya menyalin nilai bidang dan oleh itu salinannya mungkin bergantung pada objek asal. Dalam pendekatan salin dalam, kami memastikan bahawa semua objek di pohon disalin dengan mendalam, jadi salinannya tidak bergantung pada objek yang ada sebelumnya yang mungkin pernah berubah.

Dalam artikel ini, kita akan membandingkan kedua pendekatan ini dan mempelajari empat kaedah untuk melaksanakan salinan mendalam.

2. Persediaan Maven

Kami akan menggunakan tiga pergantungan Maven - Gson, Jackson, dan Apache Commons Lang - untuk menguji pelbagai cara untuk melakukan salinan mendalam.

Mari tambahkan kebergantungan ini ke pom.xml kami :

 com.google.code.gson gson 2.8.2   commons-lang commons-lang 2.6   com.fasterxml.jackson.core jackson-databind 2.9.3 

Versi terbaru Gson, Jackson, dan Apache Commons Lang boleh didapati di Maven Central.

3. Model

Untuk membandingkan kaedah yang berbeza untuk menyalin objek Java, kami memerlukan dua kelas untuk diusahakan:

class Address { private String street; private String city; private String country; // standard constructors, getters and setters }
class User { private String firstName; private String lastName; private Address address; // standard constructors, getters and setters }

4. Salinan Cetek

Salinan cetek adalah satu di mana kita hanya menyalin nilai medan dari satu objek ke objek lain:

@Test public void whenShallowCopying_thenObjectsShouldNotBeSame() { Address address = new Address("Downing St 10", "London", "England"); User pm = new User("Prime", "Minister", address); User shallowCopy = new User( pm.getFirstName(), pm.getLastName(), pm.getAddress()); assertThat(shallowCopy) .isNotSameAs(pm); }

Dalam kes ini pm! = ShallowCopy , yang bermaksud bahawa mereka adalah objek yang berbeza, tetapi masalahnya ialah apabila kita mengubah sifat - sifat alamat asal , ini juga akan mempengaruhi alamat shallowCopy .

Kami tidak akan peduli jika Alamat tidak berubah, tetapi tidak:

@Test public void whenModifyingOriginalObject_ThenCopyShouldChange() { Address address = new Address("Downing St 10", "London", "England"); User pm = new User("Prime", "Minister", address); User shallowCopy = new User( pm.getFirstName(), pm.getLastName(), pm.getAddress()); address.setCountry("Great Britain"); assertThat(shallowCopy.getAddress().getCountry()) .isEqualTo(pm.getAddress().getCountry()); }

5. Salinan Dalam

Salinan mendalam adalah alternatif untuk menyelesaikan masalah ini. Kelebihannya ialah sekurang-kurangnya setiap objek yang dapat diubah dalam grafik objek disalin secara rekursif .

Oleh kerana salinannya tidak bergantung pada objek yang dapat diubah yang dibuat sebelumnya, ia tidak akan diubah secara tidak sengaja seperti yang kita lihat dengan salinan cetek.

Pada bahagian berikut, kami akan menunjukkan beberapa implementasi salinan mendalam dan menunjukkan kelebihan ini.

5.1. Salin Pembina

Pelaksanaan pertama yang akan kita laksanakan adalah berdasarkan penyusun salinan:

public Address(Address that) { this(that.getStreet(), that.getCity(), that.getCountry()); }
public User(User that) { this(that.getFirstName(), that.getLastName(), new Address(that.getAddress())); }

Dalam pelaksanaan di atas salinan dalam, kita belum membuat baru Strings dalam pembina salinan kami kerana String adalah kelas tidak berubah-ubah.

Akibatnya, mereka tidak dapat diubah secara tidak sengaja. Mari lihat sama ada ini berfungsi:

@Test public void whenModifyingOriginalObject_thenCopyShouldNotChange() { Address address = new Address("Downing St 10", "London", "England"); User pm = new User("Prime", "Minister", address); User deepCopy = new User(pm); address.setCountry("Great Britain"); assertNotEquals( pm.getAddress().getCountry(), deepCopy.getAddress().getCountry()); }

5.2. Antaramuka yang boleh diklon

Pelaksanaan kedua berdasarkan kaedah klon yang diwarisi dari Objek . Ia dilindungi, tetapi kita perlu menggantinya sebagai umum .

Kami juga akan menambahkan antara muka penanda, Cloneable, ke kelas untuk menunjukkan bahawa kelas sebenarnya boleh diklon.

Mari tambahkan kaedah klon () ke kelas Alamat :

@Override public Object clone() { try { return (Address) super.clone(); } catch (CloneNotSupportedException e) { return new Address(this.street, this.getCity(), this.getCountry()); } }

Dan sekarang mari kita melaksanakan klon () untuk kelas Pengguna :

@Override public Object clone() { User user = null; try { user = (User) super.clone(); } catch (CloneNotSupportedException e) { user = new User( this.getFirstName(), this.getLastName(), this.getAddress()); } user.address = (Address) this.address.clone(); return user; }

Perhatikan bahawa panggilan super.clone () mengembalikan salinan objek yang cetek, tetapi kami menetapkan salinan mendalam bidang yang dapat diubah secara manual, sehingga hasilnya betul:

@Test public void whenModifyingOriginalObject_thenCloneCopyShouldNotChange() { Address address = new Address("Downing St 10", "London", "England"); User pm = new User("Prime", "Minister", address); User deepCopy = (User) pm.clone(); address.setCountry("Great Britain"); assertThat(deepCopy.getAddress().getCountry()) .isNotEqualTo(pm.getAddress().getCountry()); }

6. Perpustakaan Luar

Contoh di atas kelihatan mudah, tetapi kadangkala ia tidak berlaku sebagai penyelesaian apabila kita tidak dapat menambahkan konstruktor tambahan atau mengesampingkan kaedah klon .

Ini mungkin berlaku apabila kita tidak memiliki kod, atau ketika grafik objek begitu rumit sehingga kita tidak akan menyelesaikan projek kita tepat pada waktunya jika kita fokus untuk menulis konstruktor tambahan atau menerapkan kaedah klon pada semua kelas dalam grafik objek.

Selepas itu, apa? Dalam kes ini, kita boleh menggunakan perpustakaan luaran. Untuk mencapai salinan dalam, kita dapat membuat siri objek dan kemudian mendeseralisasikannya ke objek baru .

Mari lihat beberapa contoh.

6.1. Apache Commons Lang

Apache Commons Lang mempunyai klon SerializationUtils #, yang melakukan salinan mendalam ketika semua kelas dalam grafik objek menerapkan antara muka Serializable .

Sekiranya kaedah tersebut menemui kelas yang tidak dapat disenaraikan, ia akan gagal dan membuang SerializationException yang tidak dicentang .

Oleh kerana itu, kita perlu menambahkan antara muka Serializable ke kelas kita:

@Test public void whenModifyingOriginalObject_thenCommonsCloneShouldNotChange() { Address address = new Address("Downing St 10", "London", "England"); User pm = new User("Prime", "Minister", address); User deepCopy = (User) SerializationUtils.clone(pm); address.setCountry("Great Britain"); assertThat(deepCopy.getAddress().getCountry()) .isNotEqualTo(pm.getAddress().getCountry()); }

6.2. Serialisasi JSON Dengan Gson

Cara lain untuk membuat siri adalah dengan menggunakan serialisasi JSON. Gson adalah perpustakaan yang digunakan untuk menukar objek menjadi JSON dan sebaliknya.

Tidak seperti Apache Commons Lang, GSON tidak memerlukan antara muka Serializable untuk membuat penukaran .

Mari lihat contohnya:

@Test public void whenModifyingOriginalObject_thenGsonCloneShouldNotChange() { Address address = new Address("Downing St 10", "London", "England"); User pm = new User("Prime", "Minister", address); Gson gson = new Gson(); User deepCopy = gson.fromJson(gson.toJson(pm), User.class); address.setCountry("Great Britain"); assertThat(deepCopy.getAddress().getCountry()) .isNotEqualTo(pm.getAddress().getCountry()); }

6.3. Serialisasi JSON Dengan Jackson

Jackson adalah perpustakaan lain yang menyokong serialisasi JSON. Pelaksanaan ini akan sangat serupa dengan yang menggunakan Gson, tetapi kita perlu menambahkan konstruktor lalai ke kelas kita .

Mari lihat contoh:

@Test public void whenModifyingOriginalObject_thenJacksonCopyShouldNotChange() throws IOException { Address address = new Address("Downing St 10", "London", "England"); User pm = new User("Prime", "Minister", address); ObjectMapper objectMapper = new ObjectMapper(); User deepCopy = objectMapper .readValue(objectMapper.writeValueAsString(pm), User.class); address.setCountry("Great Britain"); assertThat(deepCopy.getAddress().getCountry()) .isNotEqualTo(pm.getAddress().getCountry()); }

7. Kesimpulannya

Pelaksanaan mana yang harus kita gunakan ketika membuat salinan mendalam? Keputusan akhir selalunya bergantung pada kelas yang akan kita salin dan sama ada kita memiliki kelas dalam grafik objek.

Seperti biasa, contoh kod lengkap untuk tutorial ini boleh didapati di GitHub.