Design Pattern

Prototype - 깊은 복사와 얕은 복사, Cloneable

Jungsoomin :) 2020. 11. 29. 20:36

얕은 복사란 객체의 참조 값을 복사해오는 것을 보통 의미하는 것으로 알고있다.

  • 원시 타입이라면 값이 대입되어 초기화된다.
  • 참조 타입이라면 참조 값이 대입된다. 원본과 같은 참조 값을 가진다.
  • 복사 본에 변경을 가하면 원본 객체에도 변화가 일어난다.

깊은 복사란 객체를 새로 생성하여 객체에 들어있는 값을 복사하는 것으로 알고 있다.

  • 원시 타입이라면 값이 대입되어 초기화된다.
  • 참조타입이라면 복사한 객체는 새로운 객체 이다.
  • 멤버변수에 참조 타입이 있다면 해당 참조타입 또한 새로 만들어줘야한다.

이 변환 과정을 쉽게 보여주는 것이 Cloneable 인터페이스를 구현한 객체와 Cloneable 인터페이스를 구현하고 있는 배열이다.

 

Java 에서는 객체 복사를 손쉽게 하기위해 clone() 메서드가 Object에 정의되어 있으며,  복사할 객체의 클래스가 Cloneable 을 구현하고 있지 않다면 CloneNotSupportedException 이 발생한다.

 

특별한 것이 아니라 , Cloneable 은 마커 인터페이스이다.

 

즉 아무런 과정없이 clone() 을 사용하게 된다면 CloneNotSupportedException 을 마주하게 된다.

 

배열은 Cloneable을 구현하고 있다. 즉 원시타입을 가진 배열복사는 복사할때 다른 객체가 도출된다.

public static void main(String[] args) throws CloneNotSupportedException {
        int[] ints = new int[]{1,2,3,4,5};

        int[] cloneInts = ints.clone();
        cloneInts[4] = 6;

        System.out.println(Arrays.toString(ints));
        System.out.println(Arrays.toString(cloneInts));
}

//
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 6]

그렇다면 복사한 객체를 얕게 복사하면 어떻게 되는지.

public static void main(String[] args) throws CloneNotSupportedException {
        int[] ints = new int[]{1,2,3,4,5};

        int[] cloneInts = ints.clone();
        cloneInts[4] = 6;

        int[] cloneInts1 = cloneInts;
        cloneInts[4] = 7;

        System.out.println(Arrays.toString(ints));
        System.out.println(Arrays.toString(cloneInts));
        System.out.println(Arrays.toString(cloneInts1));
}

//
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 7]
[1, 2, 3, 4, 7]

그럼 Cloneable 마커 인터페이스를 구현하고 있는 클래스를 이용해서 객체 복사를 하면 정확히 어떻게 되는 것인가.

  • 복사한 객체는 깊은 복사한다.
  • 복사한 객체의 멤버변수가 원시 타입이라면 값을 대입한다.
  • 복사한 객체의 멤버변수가 참조 타입이라면 얕은 복사를 한다.
  • new 연산자를 사용하지 않는다.

그러므로 원시타입의 배열을 clone() 하게 되면(배열이 Cloneable 을 구현하고 있으므로) 원본 객체가 깊은 복사 되고, 원시타입 변수들은 값이 대입되어 문제가 없이 복사가 되는 것이다.

 

Cloneable 인터페이스를 구현하지 않으면 CloneNotSupportedException 예외가 터진다.

public class Person {

    private String name;

    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

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

//
public static void main(String[] args) throws CloneNotSupportedException {
        Person person =new Person("soomin",28);
        try{
            Person clone = person.clone();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    
//
java.lang.CloneNotSupportedException: Cloneable.Person
	at java.lang.Object.clone(Native Method)
	at Cloneable.Person.clone(Person.java:23)
	at Main.main(Main.java:22)

문제가 되는 것은 Cloneable 인터페이스를 구현한 클래스를 clone() 을 호출하여 객체를 만들 때 멤버변수가 참조 값일 경우이다.

  • 해당 클래스가 가지고 있는 멤버변수나 원소는 얕은 참조를 한다.
  • 그러므로 복사본에 변경을 가하면 원본에 변화가 일어난다.
public class Person implements Cloneable {

    private String name;

    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

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

//Cloneable 인터페이스를 구현한 배열에 원소들이 참조 타입이다. 원소는 얕은 복사가 되야한다.
public static void main(String[] args) throws CloneNotSupportedException {
        Person person = new Person("soomin",28);
        Person person1 = new Person("minho", 28);

        Person[] people = new Person[]{person,person1};

        try{
            Person[] clonePeople = people.clone();
            clonePeople[0].setAge(29);

            System.out.println(Arrays.toString(clonePeople));
            System.out.println(Arrays.toString(people));
        }catch(Exception e){
            e.printStackTrace();
        }
    }
    
 //
 [Person{name='soomin', age=29}, Person{name='minho', age=28}]
[Person{name='soomin', age=29}, Person{name='minho', age=28}]

컬렉션 타입의 제네릭이 참조 변수라면 복사할때 전부 새로운 객체로 변화시켜야하며, 해당 제네릭 타입도 Cloneable을 구현해야한다.

  • 복사한 객체는 다른 객체이나 원소가 참조타입일 경우 얕은 복사를 실행한다.
public class Person implements Cloneable {

    private String name;

    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

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

//
public class People implements Cloneable {

    private List<Person> people;

    public People() {
        people = new ArrayList<>();
    }

    public People(List<Person> people) {
        this.people = people;
    }

    public List<Person> getPeople() {
        return people;
    }

    public void setPeople(Person... people) {
        for (Person person : people) {
            this.people.add(person);
        }
    }

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

//
public static void main(String[] args) throws CloneNotSupportedException {
        Person person = new Person("soomin",28);
        Person person1 = new Person("minho", 28);

        People people = new People();
        people.setPeople(person,person1);

        People clonedPeople = (People)people.clone();
        clonedPeople.getPeople().get(0).setName("SooMin");

        System.out.println(clonedPeople == people);
        clonedPeople.getPeople().forEach(System.out::println);
        people.getPeople().forEach(System.out::println);
    }
    
///로그
false
Person{name='SooMin', age=28}
Person{name='minho', age=28}
Person{name='SooMin', age=28}
Person{name='minho', age=28}

이를 회피하기 위해서는 새로운 값을 저장시켜줘야한다.

public class People implements Cloneable {

    private List<Person> people;

    public People() {
        people = new ArrayList<>();
    }

    public People(List<Person> people) {
        this.people = people;
    }

    public List<Person> getPeople() {
        return people;
    }

    public void setPeople(Person... people) {
        for (Person person : people) {
            this.people.add(person);
        }
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        List<Person> peopleList = new ArrayList<>();

        for(Person p : this.getPeople()){
            peopleList.add(p.clone());
        }
        People newPeople = new People();
        newPeople.people = peopleList;
        return newPeople;
    }
}

//
public static void main(String[] args) throws CloneNotSupportedException {
        Person person = new Person("soomin",28);
        Person person1 = new Person("minho", 28);

        People people = new People();
        people.setPeople(person,person1);

        People clonedPeople = (People)people.clone();
        clonedPeople.getPeople().get(0).setName("SooMin");

        System.out.println(clonedPeople == people);
        clonedPeople.getPeople().forEach(System.out::println);
        people.getPeople().forEach(System.out::println);
    }
    
//
false
Person{name='SooMin', age=28}
Person{name='minho', age=28}
Person{name='soomin', age=28}
Person{name='minho', age=28}

'Design Pattern' 카테고리의 다른 글

Abstract Factory  (0) 2020.11.30
Builder  (0) 2020.11.29
Prototype  (0) 2020.11.29
SingleTon Pattern  (0) 2020.11.24
Factory Method  (0) 2020.11.24