Tìm hiểu về clone() method

Clone có nghĩa là tạo ra một bản sao từ một bản gốc, trong class Object của java có 1 method là clone(). Ở bài viết này chúng ta sẽ tìm hiểu xem thực chất method này sẽ làm gì, và clone này có phải là loại clone mà bạn đang cần hay không. Tất nhiên, bạn có thể click vào phía bên trong method này và đọc doc của nó @@, tuy nhiên ở đây mình sẽ tạo một bài thực hành để có thể dễ hình dung hơn. Mình sẽ đi luôn vào phần tạo demo, và sẽ giải thích ở phần kết quả test.

Đầu tiên tạo 1 class là Item

public class Item implements Cloneable {
   public int id;
   public String name;
   public SubItem subItem;
   public ArrayList<String> listRef;

   public Item(int id, String name, SubItem subItem, ArrayList<String> listRef, Object object) {
       this.id = id;
       this.name = name;
       this.subItem = subItem;
       this.listRef = listRef;
   }

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

Ở đây cần phải implement Cloneable, nếu không sẽ bị báo lỗi CloneNotSupportedException. Ở đây ta thấy có 4 kiểu dữ liệu là int, String, SubItem và ArrayList<String>.

Class SubItem đơn giản như sau:

public class SubItem {
   public String text;

   public SubItem(String text) {
       this.text = text;
   }
}

Bây giờ đến phần test.

Test 1: Đầu tiên sẽ khởi tạo 1 đối tượng A, sau đó clone và xem kết quả:

ArrayList<String> listRef = new ArrayList<>();
listRef.add("ref1");
Item A = new Item(1, "A", new SubItem("SubA"), listRef);

Log.i("MainActivity_onCreate", "------->A=" + printObject(A));
Item B = null;
try {
   B = (Item) A.clone();
   Log.i("MainActivity_onCreate", "------->B=" + printObject(B));
} catch (CloneNotSupportedException e) {
   e.printStackTrace();
}

Kết quả

A={"subItem":{"text":"SubA"},"listRef":["ref1"],"name":"A","id":1}
B={"subItem":{"text":"SubA"},"listRef":["ref1"],"name":"A","id":1}

Ở đây ta thấy kết quả giống hệt nhau. Chưa có gì ở đây cả. Tiếp theo đến test 2.

Test 2:

ArrayList<String> listRef = new ArrayList<>();
listRef.add("ref1");
Item A = new Item(1, "A", new SubItem("SubA"), listRef);

Item B = null;
try {
   B = (Item) A.clone();
} catch (CloneNotSupportedException e) {
   e.printStackTrace();
}
B.listRef.clear();
A.subItem.text = "changeText";
A.name = "changeNameA";

Log.i("MainActivity_onCreate", "------->A=" + printObject(A));
Log.i("MainActivity_onCreate", "------->B=" + printObject(B));

Kết quả:

A={"subItem":{"text":"changeText"},"listRef":[],"name":"changeNameA","id":1}
B={"subItem":{"text":"changeText"},"listRef":[],"name":"A","id":1}

Giá trị name ở A thay đổi thì ở B không thay đổi => A và B là 2 object khác nhau. Tuy nhiên list ở B clear -> list ở A cũng clear, text trong subItem của A thay đổi thì của B cũng bị thay đổi Như vậy ta thấy rằng tuy clone nhưng A và B vẫn không độc lập, sự thay đổi của A có thể ảnh hưởng đến B và ngược lại.

Bây giờ thêm một dòng test:

Log.i("MainActivity_onCreate", "------->" + String.valueOf(A.subItem == B.subItem));

Kết quả là true, như vậy là tuy A == B là false nhưng subItem trong A và B đều trỏ đến 1 Object, đó là nguyên nhân khi thay đổi giá trị trong subItem ở A thì B cũng thay đổi, tương tự với ArrayList.

Nguyên nhân là khi clone, đối với những field là những object có “deep structure” thì những member reference ở bên trong của chúng sẽ được copy lại, và gán vào object được clone.

Vì vậy sử dụng clone như cách này sẽ chỉ tạo ra 1 bản “shallow copy” của một Object. Để tạo ra 1 bản “deep copy” của một Object có nhiều cách, nếu sử dụng clone() thì cần phải chỉnh sửa 1 chút ở trong method clone() khi override lại ở class Item

@Override
protected Object clone() throws CloneNotSupportedException {
   Item cloneItem = (Item) super.clone();
   cloneItem.subItem = (SubItem) cloneItem.subItem.clone();
   cloneItem.listRef = (ArrayList<String>) cloneItem.listRef.clone();
   return cloneItem;
}

SubItem ở đây cần phải implement Cloneable, còn ArrayList thì nó đã implement sẵn và override sẵn lại hàm clone() rồi. Ở đây trước khi return mình sẽ clone hết các object còn lại, nếu bên trong những object này lại chứa các Object khác thì lại override lại hàm clone() của nó tương tự như thế này.

Kết quả khi test lại với Test2:

A.subItem == B.subItem ? false
A={"subItem":{"text":"changeText"},"listRef":["ref1"],"name":"changeNameA","id":1}
B={"subItem":{"text":"SubA"},"listRef":[],"name":"A","id":1}

Như vậy là A và B đã hoàn toàn độc lập với nhau.

Vậy tùy theo mục đích mà bạn có thể sử dụng sao cho hợp lý.

Cảm ơn mọi người đã đọc bài, chúc mọi người tuần mới vui vẻ :xyz:

All Rights Reserved