Object cloning trong java – GP Coder (Lập trình Java)

Trong bài này, tôi sẽ giới thiệu với các bạn phương thức clone() và các cách để Clone chính xác một Object trong Java.

Copy là gì?

Như chúng ta đã biết, trong Java có 2 loại copy: Reference Copy (Copy tham chiếu) và Object Copy (Copy toàn bộ object).

Reference Copy

Đối với Reference Copy, khi chúng ta thay đổi một thuộc tính của một đối tượng thì thuộc tính của đối tượng copy cũng bị thay đổi theo. Cách này chúng ta thường gặp khi copy đối tượng thông qua toán tử bằng (=).

Ví dụ :


class Student {
	int id;
	String name;

	Student() {

	}

	Student(int id, String name) {
		this.id = id;
		this.name = name;
	}

	public void show() {
		System.out.println("Student [id=" + id + ", name=" + name + "]");
	}
}

public class ObjectCloningExample {

	public static void main(String[] args) {

		Student s1 = new Student(1, "GP Coder");
		Student s2 = s1;
		s1.show();
		s2.show();

		System.out.println("s1 and s2 are the same reference: ");
		System.out.println(s1);
		System.out.println(s2);

		System.out.println("After changed: ");
		s1.id = 4;
		s1.show();
		s2.show();
	}
}

Output của chương trình :


Student [id=1, name=GP Coder]
Student [id=1, name=GP Coder]
s1 and s2 are the same reference: 
com.gpcoder.Student@7852e922
com.gpcoder.Student@7852e922
After changed: 
Student [id=4, name=GP Coder]
Student [id=4, name=GP Coder]

Như bạn thấy, so với reference copy ( trải qua toán toán bằng ), khi ta đổi khác giá trị của đối tượng người tiêu dùng gốc thì đối tượng người dùng copy cũng biến hóa theo, do cùng tham chiếu tới 1 vùng nhớ .

Object Copy

Đối với Object Copy ( hay Object cloning ), khi ta đổi khác giá trị của một thuộc tính thì thuộc tính của đối tượng người dùng copy cũng không bị tác động ảnh hưởng .

Ví dụ :


public class ObjectCloningExample {

	public static void main(String[] args) {

		Student s1 = new Student(1, "GP Coder");
		Student s2 = new Student();
		s2.id = s1.id;
		s2.name = s1.name;
		s1.show();
		s2.show();

		System.out.println("s1 and s2 are the different reference: ");
		System.out.println(s1);
		System.out.println(s2);

		System.out.println("After changed: ");
		s1.id = 4;
		s1.show();
		s2.show();
	}
}

Output của chương trình :


Student [id=1, name=GP Coder]
Student [id=1, name=GP Coder]
s1 and s2 are the different reference: 
com.gpcoder.Student@7852e922
com.gpcoder.Student@4e25154f
After changed: 
Student [id=4, name=GP Coder]
Student [id=1, name=GP Coder]

Như bạn thấy, so với object copy, khi ta đổi khác giá trị của đối tượng người tiêu dùng gốc thì đối tượng người tiêu dùng copy cũng không bị tác động ảnh hưởng, do đối tượng người dùng copy đã được tạo trong vùng nhớ mới .

Như ví dụ trên chỉ đơn giản có 2 field: id và name nên chúng ta có thể dễ dàng gán từng thuộc tính của đối tượng gốc. Tuy nhiên, nếu đối tượng có rất nhiều thuốc tính thì sẽ rất mất thời gian xử lý và khó khăn khi mở rộng. Trong Java, chúng ta có 1 cách để copy một đối tượng dễ dàng hơn là thông qua phương thức clone().

Object Cloning

Giới thiệu clone()

Thông thường, chúng ta tạo object bằng lệnh new Object(). Object này sẽ được tạo mới trong vùng nhớ của JVM. Khác với new, clone object là quá trình tạo object từ object đã tồn tại trong vùng nhớ.

Trong java, phương thức clone(), dùng để clone 1 object từ object có sẵn. Class của đối tượng mà chúng ta muốn clone phải được implements từ interface java.lang.Cloneable. Nếu chúng ta không implements interface Cloneable, phương thức clone() sinh ra lỗi ngoại lệ CloneNotSupportedException.

Phương thức phương thức clone() được định nghĩa trong lớp Object có cú pháp như sau:


protected native Object clone() throws CloneNotSupportedException;

Phương thức clone() giảm các tác vụ xử lý để tạo ra một bản sao chính xác của một đối tượng. Nếu chúng ta thực hiện nó bằng cách sử dụng từ khoá new sẽ mất rất nhiều tiến trình xử lý được thực hiện, đó là lý do tại sao chúng ta sử dụng object cloning.

Có 2 loại cloning

  • Shallow copy/ Shallow cloning:  đối tượng copy (không sử dụng toán tử =) sẽ copy các giá trị là primitive (int, boolean, ..) và String, nhưng các thuộc tính là Object vẫn tham chiếu tới địa chỉ cũ. Vì vậy khi ta thay đổi thuộc tính chứa object trong Object gốc (originalObject) thì giá trị của thuộc tính đó trong đối tượng đã copy (copiedObject) cũng sẽ thay đổi theo. Vì vậy đôi khi sẽ xảy ra một số tình huống không mong đợi.
  • Deep copy/ Deep cloning: copy toàn bộ giá trị, và tất cả các member trong object được copy cũng tham chiếu tới địa chỉ khác. Nên khi thay đổi giá trị của attribute là object trong originalObject sẽ không làm thay đổi attribute cùng tên trong copiedObject.

Shallow CopyDeep Copy là các quá trình tạo object bằng phương pháp clone. Mặc định của method clone()Shallow Copy. Để một object được Deep Copy, chúng ta cần override lại phương thức clone() của tất cả thuộc tính là Object trong object gốc.

Ví dụ

Ví dụ Shallow cloning


package com.gpcoder.objectcloning;

class Person implements Cloneable {
	int id;
	String name;
	Address address;

	public Person(int id, String name, Address address) {
		super();
		this.id = id;
		this.name = name;
		this.address = address;
	}

	public Person clone() throws CloneNotSupportedException {
		return (Person) super.clone();
	}

	@Override
	public String toString() {
		return "Person [id=" + id + ", name=" + name + ", address=" + address + "]";
	}
}

class Address {
	String province;
	String district;

	public Address(String province, String district) {
		super();
		this.province = province;
		this.district = district;
	}

	@Override
	public String toString() {
		return "Address [province=" + province + ", district=" + district + "]";
	}
}

public class ObjectCloningExample2 {

	public static void main(String[] args) throws CloneNotSupportedException {
		Address address = new Address("Can Tho", "Ninh Kieu");
		Person person1 = new Person(1, "gpcoder", address);
		Person person2 = person1.clone();

		System.out.println(person1);
		System.out.println(person2);

		System.out.println("\nPerson1.Address and Person2.Address are the same reference: ");
		System.out.println("Person1: " + getObjectAddress(person1));
		System.out.println("Person1.Address: " + getObjectAddress(person1.address));
		System.out.println("Person2: " + getObjectAddress(person2));
		System.out.println("Person2.Address: " + getObjectAddress(person2.address));

		System.out.println("\nAfter changed: ");
		person1.id = 4;
		person1.address.district = "Binh Thuy";

		System.out.println(person1);
		System.out.println(person2);
	}

	public static String getObjectAddress(Object obj) {
		return obj.getClass().getName() + "@" + Integer.toHexString(obj.hashCode());
	}
}

Output của chương trình :


Person [id=1, name=gpcoder, address=Address [province=Can Tho, district=Ninh Kieu]]
Person [id=1, name=gpcoder, address=Address [province=Can Tho, district=Ninh Kieu]]

Person1.Address and Person2.Address are the same reference: 
Person1: com.gpcoder.objectcloning.Person@7852e922
Person1.Address: com.gpcoder.objectcloning.Address@4e25154f
Person2: com.gpcoder.objectcloning.Person@70dea4e
Person2.Address: com.gpcoder.objectcloning.Address@4e25154f

After changed: 
Person [id=4, name=gpcoder, address=Address [province=Can Tho, district=Binh Thuy]]
Person [id=1, name=gpcoder, address=Address [province=Can Tho, district=Binh Thuy]]

Như bạn thấy, lớp Person đã implement phương thức clone() từ interface Cloneable, nên chúng ta có thể sao chép tất cả các thuộc tính của đối tượng person1 sang cho person2 thông qua phương thức clone(). Đối tượng được copy sang vùng nhớ khác nên khi ta thay đổi giá trị id của person1 thì giá trị id của person2 cũng không bị thay đổi.
Tuy nhiên, lớp Address không có implement phương thức clone() nên khi ta thay đổi giá trị của address trong person1 thì giá trị của address trong person2 cũng thay đổi theo.

Ví dụ Deep cloning


package com.gpcoder.objectcloning;

class PersonDeepClone implements Cloneable {
	int id;
	String name;
	AddressDeepClone address;

	public PersonDeepClone(int id, String name, AddressDeepClone address) {
		super();
		this.id = id;
		this.name = name;
		this.address = address;
	}

	public PersonDeepClone clone() throws CloneNotSupportedException {
		PersonDeepClone personCloned = (PersonDeepClone) super.clone();
		personCloned.address = this.address.clone();
		return personCloned;
	}

	@Override
	public String toString() {
		return "Person [id=" + id + ", name=" + name + ", address=" + address + "]";
	}
}

class AddressDeepClone implements Cloneable {
	String province;
	String district;

	public AddressDeepClone(String province, String district) {
		super();
		this.province = province;
		this.district = district;
	}

	public AddressDeepClone clone() throws CloneNotSupportedException {
		return (AddressDeepClone) super.clone();
	}

	@Override
	public String toString() {
		return "Address [province=" + province + ", district=" + district + "]";
	}
}

public class ObjectCloningExample3 {

	public static void main(String[] args) throws CloneNotSupportedException {
		AddressDeepClone address = new AddressDeepClone("Can Tho", "Ninh Kieu");
		PersonDeepClone person1 = new PersonDeepClone(1, "gpcoder", address);
		PersonDeepClone person2 = person1.clone();

		System.out.println(person1);
		System.out.println(person2);

		System.out.println("\nPerson1.Address and Person2.Address are the same reference: ");
		System.out.println("Person1: " + getObjectAddress(person1));
		System.out.println("Person1.Address: " + getObjectAddress(person1.address));
		System.out.println("Person2: " + getObjectAddress(person2));
		System.out.println("Person2.Address: " + getObjectAddress(person2.address));

		System.out.println("\nAfter changed: ");
		person1.id = 4;
		person1.address.district = "Binh Thuy";

		System.out.println(person1);
		System.out.println(person2);
	}

	public static String getObjectAddress(Object obj) {
		return obj.getClass().getName() + "@" + Integer.toHexString(obj.hashCode());
	}
}

Output của chương trình :


Person [id=1, name=gpcoder, address=Address [province=Can Tho, district=Ninh Kieu]]
Person [id=1, name=gpcoder, address=Address [province=Can Tho, district=Ninh Kieu]]

Person1.Address and Person2.Address are the same reference: 
Person1: com.gpcoder.objectcloning.PersonDeepClone@7852e922
Person1.Address: com.gpcoder.objectcloning.AddressDeepClone@4e25154f
Person2: com.gpcoder.objectcloning.PersonDeepClone@70dea4e
Person2.Address: com.gpcoder.objectcloning.AddressDeepClone@5c647e05

After changed: 
Person [id=4, name=gpcoder, address=Address [province=Can Tho, district=Binh Thuy]]
Person [id=1, name=gpcoder, address=Address [province=Can Tho, district=Ninh Kieu]]

Như bạn thấy, lớp Address đã implement phương thức clone() nên khi ta thay đổi giá trị của address trong person1 thì giá trị của address trong person2 không bị ảnh hưởng.

Ưu điểm và điểm yếu kém khi sử dụng Object Cloning

Ưu điểm

  • Không cần phải gán từng thuộc tính bằng tay.
  • Tránh lặp code.
  • Dễ dàng cài đặt, và hiệu suất tốt hơn so với sử dụng reflection hay copy array.

Nhược điểm

  • Để sử dụng phương thức clone(), chúng ta phải thay đổi code bên trong class.
  • Chúng ta phải implement interface Cloneable trong khi nó có thể không có bất kỳ cài đặt nào trong đó. Chỉ sử dụng nó để báo cho JVM biết rằng chúng ta có thể thực hiện clone() trên đối tượng của chúng ta.
  • Object.clone() là protected, vì vậy chúng ta phải cung cấp phương thức clone() riêng và gián tiếp gọi Object.clone() từ nó.
  • Nếu bạn muốn viết một phương thức clone() cho một lớp con thì tất cả các superclasses của nó sẽ phải định nghĩa lại phương thức clone() hoặc kế thừa từ một lớp cha khác. Nếu không phương thức clone() sẽ lỗi hoặc đối tượng của lớp con sẽ bị tham chiếu (shallow copy).

Trên đây là những giới thiệu về phương thức clone() của interface Cloneable. Tùy theo trường hợp cụ thể mà ta sử dụng Shallow Cloning hoặc Deep Cloning cho phù hợp.

Tài liệu tham khảo:

4.8

Nếu bạn thấy hay thì hãy chia sẻ bài viết cho mọi người nhé!

Shares

Bình luận

phản hồi