Trang chủHướng dẫnCloning trong Java – Hướng dẫn chi tiết Java object clone() method
Java

Cloning trong Java – Hướng dẫn chi tiết Java object clone() method

CyStack blog 7 phút để đọc
CyStack blog22/09/2025
Locker Avatar

Chris Pham

Technical Writer

Locker logo social
Reading Time: 7 minutes

Cloning trong Java

Nhân bản là quá trình tạo ra một bản sao của một đối tượng. Lớp Object trong Java đã tích hợp sẵn phương thức clone(), cho phép trả về một bản sao của instance hiện có. Vì Object là lớp cơ sở trong Java, nên tất cả các đối tượng theo mặc định đều hỗ trợ việc nhân bản.

Nhân bản đối tượng trong Java

java clone object cloning in java

Nếu bạn muốn sử dụng phương thức clone() của Java Object, bạn phải implement interface đánh dấu java.lang.Cloneable. Nếu không, chương trình sẽ ném CloneNotSupportedException khi chạy. Ngoài ra, clone() trong lớp Object là một phương thức protected, nên bạn cần phải override nó trong lớp của mình. Dưới đây là ví dụ minh họa về nhân bản đối tượng trong Java.

package com.journaldev.cloning;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class Employee implements Cloneable {

	private int id;

	private String name;

	private Map<String, String> props;

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

	public Map<String, String> getProps() {
		return props;
	}

	public void setProps(Map<String, String> p) {
		this.props = p;
	}

	 @Override
	 public Object clone() throws CloneNotSupportedException {
	 return super.clone();
	 }

}

Chúng ta đang sử dụng phương thức clone() của Object, vì vậy lớp của chúng ta đã implement interface Cloneable. Chúng ta sẽ gọi phương thức clone() của superclass, tức là phương thức Object.clone().

Sử dụng phương thức Object.clone()

Hãy cùng tạo một chương trình thử nghiệm để sử dụng phương thức clone() của đối tượng, nhằm tạo ra một bản sao của instance.

package com.journaldev.cloning;

import java.util.HashMap;
import java.util.Map;

public class CloningTest {

	public static void main(String[] args) throws CloneNotSupportedException {

		Employee emp = new Employee();

		emp.setId(1);
		emp.setName("Pankaj");
		Map<String, String> props = new HashMap<>();
		props.put("salary", "10000");
		props.put("city", "Bangalore");
		emp.setProps(props);

		Employee clonedEmp = (Employee) emp.clone();

		// Check whether the emp and clonedEmp attributes are same or different
		System.out.println("emp and clonedEmp == test: " + (emp == clonedEmp));
		
		System.out.println("emp and clonedEmp HashMap == test: " + (emp.getProps() == clonedEmp.getProps()));
		
		// Let's see the effect of using default cloning
		
		// change emp props
		emp.getProps().put("title", "CEO");
		emp.getProps().put("city", "New York");
		System.out.println("clonedEmp props:" + clonedEmp.getProps());

		// change emp name
		emp.setName("new");
		System.out.println("clonedEmp name:" + clonedEmp.getName());

	}

}

Kết quả đầu ra:

emp and clonedEmp == test: false
emp and clonedEmp HashMap == test: true
clonedEmp props:{city=New York, salary=10000, title=CEO}
clonedEmp name:Pankaj

CloneNotSupportedException khi chạy chương trình

Nếu lớp Employee của chúng ta không implement interface Cloneable, chương trình trên sẽ ném ra ngoại lệ CloneNotSupportedException khi chạy.

Exception in thread "main" java.lang.CloneNotSupportedException: com.journaldev.cloning.Employee
	at java.lang.Object.clone(Native Method)
	at com.journaldev.cloning.Employee.clone(Employee.java:41)
	at com.journaldev.cloning.CloningTest.main(CloningTest.java:19)

java clone object CloneNotSupportedException

Hiểu về Nhân bản Đối tượng

Hãy phân tích kết quả trên và hiểu những gì đang xảy ra với phương thức clone() của Object.

  1. emp và clonedEmp == test: false: Điều này có nghĩa là emp và clonedEmp là hai đối tượng khác nhau, không tham chiếu tới cùng một đối tượng. Điều này đúng với yêu cầu của nhân bản đối tượng trong Java.
  2. emp và clonedEmp HashMap == test: true: Cả hai biến đối tượng emp và clonedEmp đều tham chiếu tới cùng một đối tượng HashMap. Đây có thể là vấn đề nghiêm trọng về tính toàn vẹn dữ liệu nếu chúng ta thay đổi giá trị bên trong đối tượng này. Bất kỳ thay đổi nào có thể ảnh hưởng tới bản sao clonedEmp vì cả hai đang tham chiếu tới cùng một đối tượng.
  3. clonedEmp props:{city=New York, salary=10000, title=CEO}: Chúng ta không thay đổi bất kỳ thuộc tính nào của clonedEmp, nhưng giá trị vẫn bị thay đổi. Điều này xảy ra vì cả emp và clonedEmp đang tham chiếu tới cùng một đối tượng bên trong. Nguyên nhân là phương thức clone() mặc định của Object tạo ra shallow copy. Điều này sẽ gây vấn đề nếu bạn muốn tạo ra các đối tượng hoàn toàn độc lập thông qua quá trình nhân bản. Do đó, cần override phương thức clone() để xử lý đúng cách.
  4. clonedEmp name: Pankaj: Chuyện gì xảy ra ở đây? Chúng ta thay đổi tên của emp, nhưng tên của clonedEmp không thay đổi. Nguyên nhân là String trong Java là không thể thay đổi. Khi gán emp.name, một chuỗi mới được tạo và tham chiếu emp.name được cập nhật (this.name = name;). Vì vậy, tên của clonedEmp vẫn không thay đổi. Bạn sẽ thấy hành vi tương tự đối với các biến kiểu primitive. Vì vậy, nhân bản mặc định của Java hoạt động tốt khi đối tượng chỉ chứa các biến primitive và immutable. Tuy nhiên, đối với các đối tượng tham chiếu, cần xử lý deep copy nếu muốn các bản sao hoàn toàn độc lập.

Các loại Nhân bản Đối tượng

Có hai loại nhân bản đối tượng trong Java: shallow cloning và deep cloning. Hãy cùng tìm hiểu từng loại và xác định cách tốt nhất để triển khai nhân bản trong chương trình Java.

  1. Shallow Cloning

    Triển khai mặc định của phương thức clone() trong Java sử dụng shallow copy. Nó dùng Reflection API để tạo bản sao của instance. Dưới đây là đoạn mã minh họa cách triển khai nhân bản nông.

@Override
 public Object clone() throws CloneNotSupportedException {
 
	 Employee e = new Employee();
	 e.setId(this.id);
	 e.setName(this.name);
	 e.setProps(this.props);
	 return e;
}
  1. Deep Cloning

Trong deep cloning, chúng ta phải sao chép từng field một cách riêng lẻ. Nếu một trường chứa các đối tượng lồng nhau như List, Map, v.v., thì chúng ta cũng phải viết mã để sao chép từng phần tử bên trong. Chính vì vậy, nó được gọi là deep cloning hay deep copy. Chúng ta có thể override phương thức clone() của lớp Employee để thực hiện deep cloning như trong đoạn mã sau.

public Object clone() throws CloneNotSupportedException {

	Object obj = super.clone(); //utilize clone Object method

	Employee emp = (Employee) obj;

	// deep cloning for immutable fields
	emp.setProps(null);
	Map<String, String> hm = new HashMap<>();
	String key;
	Iterator<String> it = this.props.keySet().iterator();
	// Deep Copy of field by field
	while (it.hasNext()) {
		key = it.next();
		hm.put(key, this.props.get(key));
	}
	emp.setProps(hm);
	
	return emp;
}

Với việc triển khai phương thức clone() này, chương trình thử nghiệm của chúng ta sẽ cho ra kết quả đầu ra như sau.

emp and clonedEmp == test: false
emp and clonedEmp HashMap == test: false
clonedEmp props:{city=Bangalore, salary=10000}
clonedEmp name:Pankaj

Nhân bản thông qua Serialization

Một cách đơn giản để thực hiện deep cloning là thông qua serialization. Tuy nhiên, serialization là một quá trình tốn kém tài nguyên, và lớp của bạn phải implement interface Serializable. Ngoài ra, tất cả các fields và superclasses cũng phải implement Serializable.

Sử dụng Apache Commons Util

Nếu bạn đã sử dụng các lớp của Apache Commons Util trong dự án và lớp của bạn có thể serializable, bạn có thể dùng phương pháp dưới đây để thực hiện deep cloning.

Employee clonedEmp = org.apache.commons.lang3.SerializationUtils.clone(emp);

Copy Constructor để Nhân bản

Chúng ta có thể định nghĩa một copy constructor để tạo bản sao của đối tượng. Vậy tại sao phải phụ thuộc hoàn toàn vào phương thức Object.clone()?

Ví dụ, chúng ta có thể tạo một copy constructor cho lớp Employee như trong đoạn mã sau.

public Employee(Employee emp) {
	
	this.setId(emp.getId());
	this.setName(emp.getName());
	
	Map<String, String> hm = new HashMap<>();
	String key;
	Iterator<String> it = emp.getProps().keySet().iterator();
	// Deep Copy of field by field
	while (it.hasNext()) {
		key = it.next();
		hm.put(key, emp.getProps().get(key));
	}
	this.setProps(hm);

}

Bất cứ khi nào chúng ta cần một bản sao của đối tượng Employee, có thể lấy bằng cách tạo instance mới dựa trên đối tượng gốc.

Tuy nhiên, việc viết copy constructor có thể là một công việc tốn thời gian nếu lớp của bạn có nhiều biến, đặc biệt là các biến primitive và immutable.

Các Thực hành Tốt nhất cho Nhân bản đối tượng trong Java

  1. Sử dụng phương thức clone() mặc định chỉ khi lớp của bạn chỉ có các biến primitive và immutable hoặc khi bạn muốn thực hiện shallow copy. Trong trường hợp kế thừa, bạn cần kiểm tra tất cả các lớp mà bạn đang kế thừa cho đến lớp Object.
  2. Định nghĩa copy constructor nếu lớp của bạn chủ yếu có các thuộc tính mutable.
  3. Tận dụng phương thức Object.clone() bằng cách gọi super.clone() trong phương thức clone() đã override, sau đó thực hiện các thay đổi cần thiết để deep copy các trường mutable.
  4. Sử dụng serialization để nhân bản nếu lớp của bạn implement Serializable. Tuy nhiên, phương pháp này sẽ ảnh hưởng đến hiệu suất, nên nên thực hiện benchmark trước khi áp dụng.
  5. Nếu bạn kế thừa từ một lớp đã định nghĩa clone() đúng cách sử dụng deep copy, bạn có thể tận dụng phương thức clone mặc định.
@Override
public Object clone() throws CloneNotSupportedException {

	Object obj = super.clone();

	Employee emp = (Employee) obj;

	// deep cloning for immutable fields
	emp.setProps(null);
	Map<String, String> hm = new HashMap<>();
	String key;
	Iterator<String> it = this.props.keySet().iterator();
	// Deep Copy of field by field
	while (it.hasNext()) {
		key = it.next();
		hm.put(key, this.props.get(key));
	}
	emp.setProps(hm);

	return emp;
}

Chúng ta có thể tạo một lớp con và tận dụng deep cloning của lớp cha như sau.

package com.journaldev.cloning;

public class EmployeeWrap extends Employee implements Cloneable {

	private String title;

	public String getTitle() {
		return title;
	}

	public void setTitle(String t) {
		this.title = t;
	}

	@Override
	public Object clone() throws CloneNotSupportedException {

		return super.clone();
	}
}

Lớp EmployeeWrap không có bất kỳ thuộc tính mutable nào và đang sử dụng triển khai phương thức clone() của lớp cha. Nếu có các trường mutable, bạn chỉ cần xử lý deep copy cho những trường đó. Dưới đây là một chương trình đơn giản để kiểm tra xem cách nhân bản này có hoạt động đúng hay không.

package com.journaldev.cloning;

import java.util.HashMap;
import java.util.Map;

public class CloningTest {

	public static void main(String[] args) throws CloneNotSupportedException {

		EmployeeWrap empWrap = new EmployeeWrap();

		empWrap.setId(1);
		empWrap.setName("Pankaj");
		empWrap.setTitle("CEO");
		
		Map<String, String> props = new HashMap<>();
		props.put("salary", "10000");
		props.put("city", "Bangalore");
		empWrap.setProps(props);

		EmployeeWrap clonedEmpWrap = (EmployeeWrap) empWrap.clone();
		
		empWrap.getProps().put("1", "1");
		
		System.out.println("empWrap mutable property value = "+empWrap.getProps());

		System.out.println("clonedEmpWrap mutable property value = "+clonedEmpWrap.getProps());
		
	}

}

Kết quả đầu ra:

empWrap mutable property value = {1=1, city=Bangalore, salary=10000}
clonedEmpWrap mutable property value = {city=Bangalore, salary=10000}

Vậy là nó hoạt động hoàn hảo như chúng ta mong đợi.

Đó là tất cả về nhân bản đối tượng trong Java. Hy vọng bạn đã hiểu phần nào về phương thức clone() của Java và cách override nó đúng cách mà không gây ra các vấn đề không mong muốn.

0 Bình luận

Đăng nhập để thảo luận

Chuyên mục Hướng dẫn

Tổng hợp các bài viết hướng dẫn, nghiên cứu và phân tích chi tiết về kỹ thuật, các xu hướng công nghệ mới nhất dành cho lập trình viên.

Đăng ký nhận bản tin của chúng tôi

Hãy trở thành người nhận được các nội dung hữu ích của CyStack sớm nhất

Xem chính sách của chúng tôi Chính sách bảo mật.

Đăng ký nhận Newsletter

Nhận các nội dung hữu ích mới nhất