Java에서 객체를 어떻게 복사합니까?


794

아래 코드를 고려하십시오.

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'

그래서, 나는 복사 할 dumdumtwo변화 dum에 영향을주지 않고 dumtwo. 그러나 위의 코드는 그렇게하지 않습니다. 에서 무언가를 변경 dum하면 동일한 변화가 발생 dumtwo합니다.

내가 말할 때 dumtwo = dumJava는 참조 만 복사 한다고 생각 합니다 . 따라서 새로운 사본을 만들어 dum할당 할 수있는 방법이 dumtwo있습니까?

답변:


611

복사 생성자를 만듭니다.

class DummyBean {
  private String dummy;

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

모든 객체에는 객체를 복사하는 데 사용할 수있는 복제 방법도 있지만 사용하지는 마십시오. 클래스를 생성하고 부적절한 클론 메소드를 수행하는 것은 너무 쉬운 방법입니다. 그렇게하려면 적어도 Joshua Bloch가 Effective Java 에서 그것에 대해 말한 내용을 읽으십시오 .


45
그러나 그는 코드를 DummyBean two = new DummyBean (one); 권리?
Chris K

12
이 방법이 딥 카피와 동일한 것을 효과적으로 수행합니까?
Matthew Piziak

124
@MatthewPiziak, 나에게-중첩 된 객체는 여전히 원본 소스 인스턴스를 참조하고 있기 때문에 딥 클론은 아닙니다. 값이 아닌 유형의 각 객체가 위와 동일한 생성자 템플릿을 제공하지 않으면 복제되지 않습니다.
SliverNinja-MSFT

17
@Timmmm : 예, 동일한 문자열을 참조하지만 변경할 수 없기 때문에 괜찮습니다. 프리미티브도 마찬가지입니다. 기본이 아닌 사용자의 경우, 반복자 호출을 재귀 적으로 복사하면됩니다. 예를 들어 DummyBean이 FooBar를 참조하면 FooBar는 생성자 FooBar (FooBar another)를 가져야하며, 더미는 this.foobar = new FooBar (another.foobar)
egaga

7
@ChristianVielma : 아니요, "johndoe"가 아닙니다. Timmmm이 말했듯이 문자열 자체는 변경할 수 없습니다. setDummy (..)를 사용하면 "johndoe"를 가리 키도록 하나의 참조를 설정하지만 하나의 참조는 설정하지 않습니다.
keuleJ

404

기본 : Java에서 객체 복사.

우리는 객체 - 가정하자 obj1, 그 두 개체가 포함되어 containedObj1containedObj2을 .
여기에 이미지 설명을 입력하십시오

얕은 복사 :
얕은 복사 instance는 동일한 클래스 의 새 클래스를 만들고 모든 필드를 새 인스턴스에 복사하여 반환합니다. Object 클래스clone메서드를 제공하고 단순 복사를 지원합니다.
여기에 이미지 설명을 입력하십시오

딥 카피 : 딥 카피 는 객체를 참조하는 객체와 함께 객체를 복사
할 때 발생합니다 . 아래 이미지는 깊은 복사가 수행 된 후의 모습 입니다. 있다뿐만 아니라 복사 ,하지만 그 안에 포함 된 개체가 아니라 복사 한. 딥 카피를 만드는 데 사용할 수 있습니다 . 불행히도,이 접근 방식에는 몇 가지 문제가 있습니다 ( 자세한 예 ).obj1obj1Java Object Serialization
여기에 이미지 설명을 입력하십시오

가능한 문제 :
clone 올바르게 구현하기가 까다 롭습니다. 방어 복사 , 복사 생성자 (@egaga 회신) 또는 정적 팩토리 메소드
를 사용하는 것이 좋습니다 .

  1. 객체가 있고 공개 clone()메소드가 있지만 컴파일 타임에 객체의 유형을 모르는 경우 문제가 있습니다. Java에는라는 인터페이스가 Cloneable있습니다. 실제로 객체를 만들려면이 인터페이스를 구현해야합니다 Cloneable. Object.cloneis protected 이므로 액세스하려면 공개 메소드로 덮어 써야 합니다.
  2. 우리가하려고 할 때 또 다른 문제가 발생 깊은 복사 (A)의 복잡한 객체를 . clone()모든 멤버 객체 변수 의 메소드도 딥 카피를 수행 한다고 가정하면 너무 가정이 위험합니다. 모든 클래스에서 코드를 제어해야합니다.

예를 들어 org.apache.commons.lang.SerializationUtils 에는 serialization ( Source )을 사용하는 딥 클론 방법이 있습니다 . Bean을 복제해야하는 경우 org.apache.commons.beanutils ( Source ) 에 몇 가지 유틸리티 메소드가 있습니다.

  • cloneBean Bean 클래스 자체가 Cloneable을 구현하지 않더라도 사용 가능한 특성 getter 및 setter를 기반으로 Bean을 복제합니다.
  • copyProperties 등록 정보 이름이 동일한 모든 경우에 대해 원점 Bean에서 대상 Bean으로 등록 정보 값을 복사합니다.

1
다른 물건 안에 무엇이 들어 있는지 설명해 주시겠습니까?
Freakyuser 2016 년

1
@Chandra Sekhar "얕은 복사는 같은 클래스의 새로운 인스턴스를 생성하고 모든 필드를 새로운 인스턴스에 복사하여 반환합니다"모든 필드를 언급하는 것은 잘못된 일입니다. bcz 객체는 복사되지 않은 참조 만 복사됩니다. 오래된 것 (원본)이 가리키는 것과 같은 물체.
JAVA

4
@sunny-Chandra의 설명이 정확합니다. 그리고 무슨 일이 일어나는지에 대한 당신의 묘사도 마찬가지입니다. "모든 필드를 복사합니다"의 의미를 잘못 이해했다고 말하고 있습니다. 필드 참조이며 참조되는 개체가 아닙니다. "모든 필드 복사" "모든 해당 참조 복사 "를 의미 합니다. "모든 필드 복사"에 대한 귀하와 동일한 오해가있는 다른 사람에게 이것이 정확히 무엇을 의미하는지 지적하는 것이 좋습니다. :)
ToolmakerSteve

2
... 객체에 대한 "포인터"와 함께 일부 하위 수준의 OO 언어로 생각하면 이러한 필드에는 객체 데이터가있는 메모리의 주소 (예 : "0x70FF1234")가 포함됩니다. 해당 주소는 복사 (할당)중인 "필드 값"입니다. 최종 결과는 두 오브젝트 모두 동일한 오브젝트를 참조하는 필드를 갖는 것입니다.
ToolmakerSteve

127

패키지 import org.apache.commons.lang.SerializationUtils;에는 다음과 같은 방법이 있습니다.

SerializationUtils.clone(Object);

예:

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

59
객체가 구현되는 한Serializable
Androiderson

2
이 경우 복제 된 개체는 원본이 정적 인 경우 원본을 참조하지 않습니다.
단테

8
객체를 복제하기위한 타사 라이브러리!
Khan

2
@Khan, "타사 라이브러리"는 완전히 별개의 토론입니다! : D
Charles Wood

103

다음과 같이 따르십시오.

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

다른 객체를 얻으려면 어디에서나 간단하게 복제를 수행하십시오. 예 :

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
이것을 테스트 했습니까? 나는 이것을 내 프로젝트에 사용할 수 있으며 정확해야합니다.
안개가 자욱한

2
@misty 나는 그것을 테스트했다. 내 생산 응용 프로그램에 완벽하게 작동
안드리 코발 추크

복제 후 원본 객체를 수정하면 복제본도 수정됩니다.
Sibish

4
이것은 요청 된 깊은 사본 이 아니기 때문에 잘못되었습니다 .
Bluehorn

1
이 메소드는 복제 가능한 객체를 가리키는 포인터를 복제하지만 두 객체 내부의 모든 속성은 동일합니다. 따라서 메모리에 새 객체가 생성되지만 각 객체 내부의 데이터는 메모리의 동일한 데이터
Omar HossamEldin

40

Reflection API 사용에 대한 답변이없는 이유는 무엇입니까?

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

정말 간단합니다.

편집 : 재귀를 통해 자식 개체 포함

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

이것은 훨씬 나아 보이지만 setAccessible (true)이 실패 할 수 있으므로 최종 필드 만 고려하면되므로 field.set (clone, field.get (obj))를 별도로 호출 할 때 발생하는 예외 IllegalAccessException을 별도로 처리해야 할 수도 있습니다.
최대

1
나는 그것을 너무 좋아했지만 제네릭을 사용하도록 리팩터링 할 수 있습니까? private static <T> T cloneObject (T obj) {....}
Adelin

2
속성에서 부모에 대한 참조가있을 때 문제가된다고 생각합니다. Class A { B child; } Class B{ A parent; }
nhthai

이 상황에서도 실패하고 처리해야하며 내일 가지고 놀 것입니다. class car { car car = new car(); }
Ján Яabčan

2
오류가 발생하기 쉽습니다. 컬렉션을 어떻게 다룰 지 잘 모르겠습니다
ACV

31

Google의 JSON 라이브러리를 사용하여 직렬화 한 다음 직렬화 된 객체의 새 인스턴스를 만듭니다. 몇 가지 제한 사항으로 딥 카피를 수행합니다.

  • 재귀 참조는있을 수 없습니다

  • 이종 유형의 배열을 복사하지 않습니다.

  • 배열과 목록을 입력해야합니다. 그렇지 않으면 인스턴스화 할 클래스를 찾을 수 없습니다

  • 자신을 선언하는 클래스에서 문자열을 캡슐화해야 할 수도 있습니다.

또한이 클래스를 사용하여 사용자 기본 설정, 창 및 런타임시 다시로드하지 않을 내용을 저장합니다. 사용하기 쉽고 효과적입니다.

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

이것은 잘 작동합니다. 그러나 List <Integer>와 같은 것을 복제하려고하면 조심하십시오. 내 정수는 Doubles, 100.0으로 바뀌 었습니다. 왜 그런지 이해하는 데 시간이 오래 걸렸습니다. 해결책은 Integer를 하나씩 복제하고주기에 목록에 추가하는 것입니다.
paakjis


14

Cloneable수업에 코드 추가 및 아래

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

이것을 사용하십시오 clonedObject = (YourClass) yourClassObject.clone();



12

이것도 작동합니다. 가정 모델

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

먼저 compile 'com.google.code.gson:gson:2.8.1'앱에 gradle & sync를 추가 하십시오. 그때

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

transient액세스 수정 자 후 키워드를 사용하여 필드 사용을 제외 할 수 있습니다 .

참고 : 이것은 나쁜 습관입니다. 또한 사용하지 않는 것이 좋습니다 Cloneable또는 JavaSerialization이 느리고 고장입니다. 최적의 성능을 위해 쓰기 복사 생성자 심판 .

같은 것

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

90000 회 반복 테스트 통계 :
라인 UserAccount clone = gson.fromJson(gson.toJson(aO), UserAccount.class);소요 808ms

라인 UserAccount clone = new UserAccount(aO);1ms 미만

결론 : 보스가 미쳤고 속도를 선호하는 경우 gson을 사용하십시오. 품질을 원하면 두 번째 사본 생성자를 사용하십시오.

Android Studio에서 복사 생성자 코드 생성기 플러그인 을 사용할 수도 있습니다 .


그것이 나쁜 연습이라면 왜 그것을 제안 했습니까?
Parth Mehrotra

감사합니다 @ParthMehrotra
Qamar


9

심층 복제 유틸리티를 사용하십시오.

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

이것은 모든 Java 객체를 딥 복사하고 https://github.com/kostaskougios/cloning 에서 확인 하십시오.


1
사용자 정의 클래스를 사용하여 작동하지 않았습니다. 다음 예외가 발생합니다. java.lang.NoClassDefFoundError : sun.reflect.ReflectionFactory
stefanjunker

9

딥 클로닝은 Cloneable인터페이스 구현 및 clone()메서드 재정의 가 필요한 답변 입니다.

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

당신은 이것을 이렇게 부를 것입니다 DummyBean dumtwo = dum.clone();


2
dummy, a String는 변경할 수 없습니다. 복사 할 필요가 없습니다
Steve Kuo

7

그러기 위해서는 어떤 방법 으로든 객체를 복제해야합니다. Java에는 복제 메커니즘이 있지만 필요하지 않은 경우에는 사용하지 마십시오. 복사 작업을 수행하는 복사 방법을 만든 후 다음을 수행하십시오.

dumtwo = dum.copy();

다음 은 복사를 수행하기위한 다양한 기술에 대한 조언입니다.


6

명시 적으로 복사하는 것 이외의 다른 방법은 객체를 변경할 수 없게 만드는 것입니다 ( set또는 다른 변경자 방법 없음). 이런 식으로 질문은 결코 일어나지 않습니다. 더 큰 물체에서는 불변성이 더 어려워 지지만 그 반대편은 작은 물체와 복합 재료로 나뉘는 방향으로 당신을 밀어 붙인다는 것입니다.


5

egaga의 생성자 복사 방법에 대한 대안 . 이미 POJO가있을 수 있으므로 copy()초기화 된 객체의 복사본을 반환하는 다른 메서드 를 추가하기 만하면 됩니다.

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
}

이미 가지고 DummyBean있고 사본을 원할 경우 :

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());

산출:

bean1: peet 2
bean2: peet 2

bean1: koos 88
bean2: peet 2

그러나 둘 다 잘 작동합니다. 궁극적으로 당신에게 달려 있습니다 ...



3

http://x-stream.github.io/ 에서 XStream으로 딥 카피를 자동으로 수행 할 수 있습니다 .

XStream은 객체를 XML로 직렬화하고 다시 되 돌리는 간단한 라이브러리입니다.

프로젝트에 추가하십시오 (maven을 사용하는 경우)

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

그때

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

이를 통해 복제 인터페이스를 구현할 필요없이 사본을 얻을 수 있습니다.


29
XML로 /에서 XML로 변환하는 것은 그리 우아하지 않습니다. 가볍게 두는 것!
Timmmm

java.beans.XMLEncoderXML로 직렬화되는 표준 Java API를 살펴보십시오 (심도 깊은 복사 목적은 아니지만).
Jaime Hablutzel

1
이것이 얼마나 무거운 지 아십니까?
mahieddine

1
타사 라이브러리를 추가하고 성능에 큰 영향을 줄 수있는 객체 직렬화를 수행해야하기 때문에 내 의견으로는 많은 오버 헤드가 발생합니다.
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();
   }

  }
}

그리고 당신의 코드에서 :

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

2
예외를 잡아 내려 던지지 않으면 선언에 "throws CloneNotSupportedException"설정에 아무런 의미가 없습니다. 따라서 제거 할 수 있습니다.
Christian

2

복사하려는 객체를 전달하고 원하는 객체를 가져옵니다.

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;

    }

이제 objDest원하는 객체로 구문 분석하십시오 .

행복한 코딩!


1

메소드 를 구현 Cloneable하고 사용 하려고 시도 할 수 있습니다 clone(). 그러나, 당신은 당신이해야 복제 방법을 사용하는 경우 - 항상 무시 - 표준으로 Objectpublic Object clone()방법을.


1

소스 파일에 주석을 추가 할 수 있습니다, 같은 주석 프로세서 또는 코드 생성기 이 하나 를 사용할 수 있습니다.

import net.zerobuilder.BeanBuilder

@BeanBuilder
public class DummyBean { 
  // bean stuff
}

얕은 복사본을 만드는 DummyBeanBuilders정적 메서드가있는 클래스 가 dummyBeanUpdater수동으로 생성되는 것과 같은 방식으로 생성됩니다.

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

0

gson객체 복제에 사용 합니다.

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

내가 객체가 있다고 가정 person낭포

Person copyPerson = copyObject(person);
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.