Làm cách nào để sao chép một đối tượng trong Java?


794

Hãy xem xét mã dưới đây:

DummyBean dum = new DummyBean();
dum.setDummy("foo");
System.out.println(dum.getDummy()); // prints 'foo'

DummyBean dumtwo = dum;
System.out.println(dumtwo.getDummy()); // prints 'foo'

dum.setDummy("bar");
System.out.println(dumtwo.getDummy()); // prints 'bar' but it should print 'foo'

Vì vậy, tôi muốn sao chép dumđến dumtwovà thay đổi dummà không ảnh hưởng dumtwo. Nhưng đoạn mã trên không làm được điều đó. Khi tôi thay đổi một cái gì đó dum, sự thay đổi tương tự cũng xảy ra dumtwo.

Tôi đoán, khi tôi nói dumtwo = dum, Java chỉ sao chép tham chiếu . Vì vậy, có cách nào để tạo một bản sao mới dumvà gán nó cho dumtwo?

Câu trả lời:


611

Tạo một hàm tạo sao chép:

class DummyBean {
  private String dummy;

  public DummyBean(DummyBean another) {
    this.dummy = another.dummy; // you can access  
  }
}

Mọi đối tượng cũng có một phương thức nhân bản có thể được sử dụng để sao chép đối tượng, nhưng không sử dụng nó. Thật quá dễ dàng để tạo một lớp và thực hiện phương thức nhân bản không đúng. Nếu bạn định làm điều đó, hãy đọc ít nhất Joshua Bloch nói gì về nó trong Java hiệu quả .


45
Nhưng sau đó anh ta phải đổi mã của mình thành DummyBean hai = DummyBean mới (một); Đúng?
Chris K

12
Liệu phương pháp này có hiệu quả thực hiện điều tương tự như một bản sao sâu?
Matthew Piziak

124
@MatthewPiziak, với tôi - đây sẽ không phải là một bản sao sâu vì mọi đối tượng lồng nhau vẫn sẽ tham chiếu thể hiện nguồn gốc, không phải là một bản sao trừ khi mỗi đối tượng (loại không giá trị) cung cấp cùng một mẫu hàm tạo như trên.
SliverNinja - MSFT

17
@Timmmm: Vâng, họ sẽ tham chiếu cùng một Chuỗi nhưng vì nó không thay đổi nên không sao. Tương tự như vậy đối với người nguyên thủy. Đối với người không phải là người nguyên thủy, bạn chỉ cần thực hiện sao chép cuộc gọi đối xứng theo cách đệ quy. ví dụ: Nếu DummyBean tham chiếu FooBar thì FooBar nên có FooBar (FooBar khác), và hình nộm nên gọi đây.foobar = new FooBar (
other.foobar

7
@ChristianVielma: Không, nó sẽ không là "johndoe". Giống như Timmmm đã nói, bản thân chuỗi là bất biến. Với một, setDummy (..) bạn đặt tham chiếu trong một để trỏ đến "johndoe", nhưng không phải là một trong một.
keuleJ

404

Cơ bản: Sao chép đối tượng trong Java.

Chúng ta hãy giả sử một đối tượng- obj1, có chứa hai đối tượng, gồmObj1chứaObj2 .
nhập mô tả hình ảnh ở đây

sao chép nông: sao chép
nông tạo một cái mới instancecủa cùng một lớp và sao chép tất cả các trường sang thể hiện mới và trả về nó. Lớp đối tượng cung cấp một clonephương thức và cung cấp hỗ trợ cho việc sao chép nông.
nhập mô tả hình ảnh ở đây


Sao chép sâu : Một bản sao sâu xảy ra khi một đối tượng được sao chép cùng với các đối tượng mà nó đề cập đến . Dưới đây hình ảnh hiển thị obj1sau khi một bản sao sâu đã được thực hiện trên nó. Không chỉ obj1được sao chép , mà các đối tượng trong đó cũng được sao chép. Chúng ta có thể sử dụng Java Object Serializationđể tạo một bản sao sâu. Thật không may, cách tiếp cận này cũng có một số vấn đề ( ví dụ chi tiết ).
nhập mô tả hình ảnh ở đây

Vấn đề có thể xảy ra:
clone là khó khăn để thực hiện chính xác.
Tốt hơn là sử dụng Sao chép phòng thủ , sao chép hàm tạo (dưới dạng trả lời @egaga) hoặc phương thức nhà máy tĩnh .

  1. Nếu bạn có một đối tượng, mà bạn biết có một clone()phương thức công khai , nhưng bạn không biết loại đối tượng tại thời điểm biên dịch, thì bạn có vấn đề. Java có một giao diện được gọi là Cloneable. Trong thực tế, chúng ta nên thực hiện giao diện này nếu chúng ta muốn tạo một đối tượng Cloneable. Object.cloneđược bảo vệ , vì vậy chúng ta phải ghi đè lên nó bằng một phương thức công khai để có thể truy cập được.
  2. Một vấn đề khác phát sinh khi chúng ta thử sao chép sâu một đối tượng phức tạp . Giả sử rằng clone()phương thức của tất cả các biến đối tượng thành viên cũng sao chép sâu, điều này quá rủi ro cho một giả định. Bạn phải kiểm soát mã trong tất cả các lớp.

Ví dụ: org.apache.commons.lang.SerializationUtils sẽ có phương thức cho Deep clone bằng cách sử dụng tuần tự hóa ( Nguồn ). Nếu chúng ta cần sao chép Bean thì có một vài phương thức tiện ích trong org.apache.commons.beanutils ( Nguồn ).

  • cloneBean sẽ sao chép một bean dựa trên các getters và setters thuộc tính có sẵn, ngay cả khi chính lớp bean không thực hiện Clonizable.
  • copyProperties sẽ Sao chép các giá trị thuộc tính từ bean gốc sang bean đích cho tất cả các trường hợp tên thuộc tính giống nhau.

1
Bạn có thể giải thích những gì được chứa trong một đối tượng khác?
Freakyuser

1
@Chandra Sekhar "sao chép nông tạo một thể hiện mới của cùng một lớp và sao chép tất cả các trường sang thể hiện mới và trả về nó" thật sai khi đề cập đến tất cả các trường, các đối tượng bcz không được sao chép chỉ các tham chiếu được sao chép vào các điểm cùng một đối tượng mà cái cũ (bản gốc) đã chỉ vào.
JAVA

4
@sunny - Mô tả của Chandra là chính xác. Và mô tả của bạn về những gì xảy ra; Tôi đang nói rằng bạn hiểu sai về ý nghĩa của "bản sao tất cả các lĩnh vực". Trường tham chiếu, nó không phải là đối tượng được đề cập. "Sao chép tất cả các trường" có nghĩa là "sao chép tất cả các tham chiếu đó". Thật tốt khi bạn chỉ ra chính xác điều này có nghĩa là gì, đối với bất kỳ ai khác có cùng cách hiểu sai như bạn, về tuyên bố "sao chép tất cả các trường". :)
ToolmakerSteve

2
... Nếu chúng tôi nghĩ về một số ngôn ngữ OO cấp thấp hơn, với "con trỏ" cho các đối tượng, thì một trường như vậy sẽ chứa địa chỉ trong bộ nhớ (chẳng hạn như "0x70FF1234") mà tại đó dữ liệu đối tượng được tìm thấy. Địa chỉ đó là "giá trị trường" đang được sao chép (được gán). Bạn đúng rằng kết quả cuối cùng là cả hai đối tượng đều có các trường tham chiếu (trỏ vào) cùng một đối tượng.
ToolmakerSteve

127

Trong gói import org.apache.commons.lang.SerializationUtils;có một phương thức:

SerializationUtils.clone(Object);

Thí dụ:

this.myObjectCloned = SerializationUtils.clone(this.object);

59
Miễn là đối tượng thực hiệnSerializable
Androiderson

2
Trong trường hợp này, đối tượng nhân bản không có tham chiếu đến bản gốc, nếu đối tượng cuối cùng là tĩnh.
Dante

8
Một thư viện bên thứ ba chỉ để sao chép đối tượng!
Khan

2
@Khan, "một thư viện bên thứ ba chỉ để" là một cuộc thảo luận hoàn toàn riêng biệt! : D
Charles Wood

103

Chỉ cần làm theo như dưới đây:

public class Deletable implements Cloneable{

    private String str;
    public Deletable(){
    }
    public void setStr(String str){
        this.str = str;
    }
    public void display(){
        System.out.println("The String is "+str);
    }
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

và bất cứ nơi nào bạn muốn để có được một đối tượng khác, đơn giản thực hiện nhân bản. ví dụ:

Deletable del = new Deletable();
Deletable delTemp = (Deletable ) del.clone(); // this line will return you an independent
                                 // object, the changes made to this object will
                                 // not be reflected to other object

1
Bạn đã kiểm tra điều này? Tôi có thể sử dụng điều này cho dự án của tôi và điều quan trọng là phải chính xác.
sương mù

2
@misty Tôi đã thử nó. Hoạt động hoàn hảo trên ứng dụng sản xuất của tôi
Andrii Kovalchuk

Sau khi nhân bản, khi bạn sửa đổi đối tượng ban đầu, nó cũng sửa đổi bản sao.
Sibish

4
Điều này là sai ở chỗ nó không phải là một bản sao sâu được yêu cầu.
Bluehorn

1
Phương thức này sao chép con trỏ trỏ đến đối tượng có thể nhân bản, nhưng tất cả các thuộc tính bên trong cả hai đối tượng đều giống nhau, do đó, có một đối tượng mới được tạo trong bộ nhớ, nhưng dữ liệu bên trong mỗi đối tượng là cùng một dữ liệu từ bộ nhớ
Omar HossamEldin

40

Tại sao không có câu trả lời cho việc sử dụng API Reflection?

private static Object cloneObject(Object obj){
        try{
            Object clone = obj.getClass().newInstance();
            for (Field field : obj.getClass().getDeclaredFields()) {
                field.setAccessible(true);
                field.set(clone, field.get(obj));
            }
            return clone;
        }catch(Exception e){
            return null;
        }
    }

Nó thực sự đơn giản.

EDIT: Bao gồm đối tượng con thông qua đệ quy

private static Object cloneObject(Object obj){
        try{
            Object clone = obj.getClass().newInstance();
            for (Field field : obj.getClass().getDeclaredFields()) {
                field.setAccessible(true);
                if(field.get(obj) == null || Modifier.isFinal(field.getModifiers())){
                    continue;
                }
                if(field.getType().isPrimitive() || field.getType().equals(String.class)
                        || field.getType().getSuperclass().equals(Number.class)
                        || field.getType().equals(Boolean.class)){
                    field.set(clone, field.get(obj));
                }else{
                    Object childObj = field.get(obj);
                    if(childObj == obj){
                        field.set(clone, clone);
                    }else{
                        field.set(clone, cloneObject(field.get(obj)));
                    }
                }
            }
            return clone;
        }catch(Exception e){
            return null;
        }
    }

Điều này có vẻ tốt hơn nhiều, nhưng bạn chỉ cần xem xét các trường cuối cùng là setAccessible (true) có thể không thành công, vì vậy có thể bạn cần xử lý riêng ngoại lệ IllegalAccessException được ném khi gọi riêng trường.set (clone, field.get (obj)).
Tối đa

1
Tôi thích nó rất nhiều nhưng bạn có thể cấu trúc lại nó để sử dụng thuốc generic không? riêng tư <T> T cloneObject (T obj) {....}
Adelin

2
Tôi nghĩ rằng đó là vấn đề khi chúng tôi có tham khảo từ các thuộc tính để nó cha mẹ: Class A { B child; } Class B{ A parent; }
nhthai

Nó không thành công ngay cả trong tình huống này, cần phải xử lý, tôi sẽ chơi với nó vào ngày mai. class car { car car = new car(); }
Ján abčan

2
Đây là lỗi dễ xảy ra. Không chắc nó sẽ xử lý các bộ sưu tập như thế nào
ACV

31

Tôi sử dụng thư viện JSON của Google để tuần tự hóa nó sau đó tạo một phiên bản mới của đối tượng được tuần tự hóa. Nó sao chép sâu với một vài hạn chế:

  • không thể có bất kỳ tài liệu tham khảo đệ quy nào

  • nó sẽ không sao chép các mảng khác nhau

  • các mảng và danh sách nên được gõ hoặc nó sẽ không tìm thấy lớp để khởi tạo

  • bạn có thể cần gói gọn các chuỗi trong một lớp bạn tự khai báo

Tôi cũng sử dụng lớp này để lưu các tùy chọn người dùng, cửa sổ và không cần tải lại khi chạy. Nó rất dễ sử dụng và hiệu quả.

import com.google.gson.*;

public class SerialUtils {

//___________________________________________________________________________________

public static String serializeObject(Object o) {
    Gson gson = new Gson();
    String serializedObject = gson.toJson(o);
    return serializedObject;
}
//___________________________________________________________________________________

public static Object unserializeObject(String s, Object o){
    Gson gson = new Gson();
    Object object = gson.fromJson(s, o.getClass());
    return object;
}
       //___________________________________________________________________________________
public static Object cloneObject(Object o){
    String s = serializeObject(o);
    Object object = unserializeObject(s,o);
    return object;
}
}

Điều này làm việc tuyệt vời. Nhưng xem ra nếu bạn cố gắng sao chép một cái gì đó như Danh sách <Integer>. Nó sẽ có lỗi, Số nguyên của tôi đã biến thành Nhân đôi, 100.0. Phải mất một lúc lâu tôi mới hiểu tại sao họ lại như vậy. Giải pháp là sao chép số nguyên từng cái một và thêm vào danh sách theo chu kỳ.
paakjis


14

Thêm Cloneablevà bên dưới mã vào lớp của bạn

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

Dùng cái này clonedObject = (YourClass) yourClassObject.clone();


12

Đúng. Bạn cần phải sao chép sâu đối tượng của bạn.


1
Như vậy, nó thậm chí không phải là một bản sao.
Michael Myers

Đây có lẽ là câu trả lời ít hữu ích nhất tôi từng thấy trên stackoverflow.
Cyril

12

Điều này làm việc quá. Giả sử mô hình

class UserAccount{
   public int id;
   public String name;
}

Đầu tiên thêm compile 'com.google.code.gson:gson:2.8.1'vào ứng dụng của bạn> gradle & sync. Sau đó

Gson gson = new Gson();
updateUser = gson.fromJson(gson.toJson(mUser),UserAccount.class);

Bạn có thể loại trừ sử dụng một trường bằng cách sử dụng transienttừ khóa sau khi sửa đổi truy cập.

Lưu ý: Đây là thực hành xấu. Cũng không nên sử dụng Cloneablehoặc JavaSerializationnó chậm và hỏng. Viết constructor sao chép cho hiệu suất tốt nhất ref .

Cái gì đó như

class UserAccount{
        public int id;
        public String name;
        //empty constructor
        public UserAccount(){}
        //parameterize constructor
        public UserAccount(int id, String name) {
            this.id = id;
            this.name = name;
        }

        //copy constructor
        public UserAccount(UserAccount in){
            this(in.id,in.name);
        }
    }

Kiểm tra số liệu thống kê của 90000 lần lặp:
Dòng UserAccount clone = gson.fromJson(gson.toJson(aO), UserAccount.class);mất 808ms

Dòng UserAccount clone = new UserAccount(aO);mất ít hơn 1ms

Kết luận: Sử dụng gson nếu sếp của bạn điên và bạn thích tốc độ. Sử dụng constructor sao chép thứ hai nếu bạn thích chất lượng.

Bạn cũng có thể sử dụng plugin trình tạo mã sao chép trong Android Studio.


Tại sao bạn đề nghị nó nếu nó thực hành xấu?
Parth Mehrotra

Cảm ơn @ParthMehrotra hiện đã được cải thiện
Qamar


9

Sử dụng tiện ích nhân bản sâu:

SomeObjectType copy = new Cloner().deepClone(someObject);

Điều này sẽ sao chép sâu bất kỳ đối tượng java nào, hãy kiểm tra tại https://github.com/kostaskougios/claming


1
đã không làm việc cho tôi bằng cách sử dụng một lớp tùy chỉnh. nhận ngoại lệ sau: java.lang.NoClassDefFoundError: sun.reflect.ReflectionFactory
stefanjunker

9

Nhân bản sâu là câu trả lời của bạn, yêu cầu thực hiện Cloneablegiao diện và ghi đè clone()phương thức.

public class DummyBean implements Cloneable {

   private String dummy;

   public void setDummy(String dummy) {
      this.dummy = dummy;
   }

   public String getDummy() {
      return dummy;
   }

   @Override
   public Object clone() throws CloneNotSupportedException {
      DummyBean cloned = (DummyBean)super.clone();
      cloned.setDummy(cloned.getDummy());
      // the above is applicable in case of primitive member types like String 
      // however, in case of non primitive types
      // cloned.setNonPrimitiveType(cloned.getNonPrimitiveType().clone());
      return cloned;
   }
}

Bạn sẽ gọi nó như thế này DummyBean dumtwo = dum.clone();


2
dummy, a String, là bất biến, bạn không cần phải sao chép nó
Steve Kuo

7

Để làm điều đó bạn phải nhân bản đối tượng theo một cách nào đó. Mặc dù Java có cơ chế nhân bản, nhưng đừng sử dụng nó nếu bạn không phải làm vậy. Tạo một phương thức sao chép mà bản sao làm việc cho bạn, và sau đó thực hiện:

dumtwo = dum.copy();

Dưới đây là một số lời khuyên thêm về các kỹ thuật khác nhau để hoàn thành một bản sao.


6

Khác với việc sao chép rõ ràng, một cách tiếp cận khác là làm cho đối tượng không thay đổi (không sethoặc các phương thức trình biến đổi khác). Theo cách này, câu hỏi không bao giờ phát sinh. Sự bất biến trở nên khó khăn hơn với các vật thể lớn hơn, nhưng mặt khác của nó là đẩy bạn theo hướng phân tách thành các vật thể nhỏ và vật liệu tổng hợp.


5

Thay thế cho phương pháp xây dựng bản sao của egaga . Bạn có thể đã có POJO, vì vậy chỉ cần thêm một phương thức khác copy()trả về một bản sao của đối tượng được khởi tạo.

class DummyBean {
    private String dummyStr;
    private int dummyInt;

    public DummyBean(String dummyStr, int dummyInt) {
        this.dummyStr = dummyStr;
        this.dummyInt = dummyInt;
    }

    public DummyBean copy() {
        return new DummyBean(dummyStr, dummyInt);
    }

    //... Getters & Setters
}

Nếu bạn đã có DummyBeanvà muốn có một bản sao:

DummyBean bean1 = new DummyBean("peet", 2);
DummyBean bean2 = bean1.copy(); // <-- Create copy of bean1 

System.out.println("bean1: " + bean1.getDummyStr() + " " + bean1.getDummyInt());
System.out.println("bean2: " + bean2.getDummyStr() + " " + bean2.getDummyInt());

//Change bean1
bean1.setDummyStr("koos");
bean1.setDummyInt(88);

System.out.println("bean1: " + bean1.getDummyStr() + " " + bean1.getDummyInt());
System.out.println("bean2: " + bean2.getDummyStr() + " " + bean2.getDummyInt());

Đầu ra:

bean1: peet 2
bean2: peet 2

bean1: koos 88
bean2: peet 2

Nhưng cả hai đều hoạt động tốt, cuối cùng thì tùy bạn ...


3
class DB {
  private String dummy;

  public DB(DB one) {
    this.dummy = one.dummy; 
  }
}

3

Bạn có thể sao chép sâu tự động với XStream, từ http://x-stream.github.io/ :

XStream là một thư viện đơn giản để tuần tự hóa các đối tượng thành XML và quay lại.

Thêm nó vào dự án của bạn (nếu sử dụng maven)

<dependency>
    <groupId>com.thoughtworks.xstream</groupId>
    <artifactId>xstream</artifactId>
    <version>1.3.1</version>                
</dependency>

Sau đó

DummyBean dum = new DummyBean();
dum.setDummy("foo");
DummyBean dumCopy = (DummyBean) XSTREAM.fromXML(XSTREAM.toXML(dum));

Với điều này, bạn có một bản sao mà không cần phải thực hiện bất kỳ giao diện nhân bản nào.


29
Chuyển đổi sang / từ XML có vẻ không ... thanh lịch. Nói một cách nhẹ nhàng!
Timmmm

Hãy xem java.beans.XMLEncoderAPI Java tiêu chuẩn cũng tuần tự hóa thành XML (mặc dù không chính xác cho mục đích sao chép sâu).
Jaime Hablutzel

1
Bạn có nhận ra cái này nặng bao nhiêu không?
mahieddine

1
Theo quan điểm của tôi, bạn nên sử dụng nhiều thư viện của bên thứ 3 và thực hiện tuần tự hóa đối tượng mà rất có thể có tác động lớn đến hiệu suất.
NiThDi

2
public class MyClass implements Cloneable {

private boolean myField= false;
// and other fields or objects

public MyClass (){}

@Override
public MyClass clone() throws CloneNotSupportedException {
   try
   {
       MyClass clonedMyClass = (MyClass)super.clone();
       // if you have custom object, then you need create a new one in here
       return clonedMyClass ;
   } catch (CloneNotSupportedException e) {
       e.printStackTrace();
       return new MyClass();
   }

  }
}

và trong mã của bạn:

MyClass myClass = new MyClass();
// do some work with this object
MyClass clonedMyClass = myClass.clone();

2
Không có điểm nào trong tập hợp "ném CloneNotSupportedException" trong khai báo nếu bạn thử bắt ngoại lệ và không bị ném lên. Vì vậy, bạn có thể chỉ cần loại bỏ nó.
Christian

2

Truyền đối tượng mà bạn muốn sao chép và lấy đối tượng bạn muốn:

private Object copyObject(Object objSource) {
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(objSource);
            oos.flush();
            oos.close();
            bos.close();
            byte[] byteData = bos.toByteArray();
            ByteArrayInputStream bais = new ByteArrayInputStream(byteData);
            try {
                objDest = new ObjectInputStream(bais).readObject();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return objDest;

    }

Bây giờ phân tích objDestđối tượng mong muốn.

Chúc mừng mã hóa!


1

Bạn có thể thử thực hiện Cloneablevà sử dụng clone()phương pháp; Tuy nhiên, nếu bạn sử dụng phương pháp nhân bản bạn nên - theo tiêu chuẩn - ghi đè luôn luôn Object's public Object clone()phương pháp.


1

Nếu bạn có thể thêm chú thích vào tệp nguồn, bộ xử lý chú thích hoặc trình tạo mã như thế này có thể được sử dụng.

import net.zerobuilder.BeanBuilder

@BeanBuilder
public class DummyBean { 
  // bean stuff
}

Một lớp DummyBeanBuilderssẽ được tạo, trong đó có một phương thức tĩnh dummyBeanUpdaterđể tạo các bản sao nông, giống như cách bạn làm bằng tay.

DummyBean bean = new DummyBean();
// Call some setters ...
// Now make a copy
DummyBean copy = DummyBeanBuilders.dummyBeanUpdater(bean).done();

0

Sử dụng gsonđể nhân đôi một đối tượng.

public static <T>T copyObject(Object object){
    Gson gson = new Gson();
    JsonObject jsonObject = gson.toJsonTree(object).getAsJsonObject();
    return gson.fromJson(jsonObject,(Type) object.getClass());
}

Giả sử tôi có một đối tượng person. Vì vậy

Person copyPerson = copyObject(person);
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.