얕은 복사란 객체의 참조 값을 복사해오는 것을 보통 의미하는 것으로 알고있다.
- 원시 타입이라면 값이 대입되어 초기화된다.
- 참조 타입이라면 참조 값이 대입된다. 원본과 같은 참조 값을 가진다.
- 복사 본에 변경을 가하면 원본 객체에도 변화가 일어난다.
깊은 복사란 객체를 새로 생성하여 객체에 들어있는 값을 복사하는 것으로 알고 있다.
- 원시 타입이라면 값이 대입되어 초기화된다.
- 참조타입이라면 복사한 객체는 새로운 객체 이다.
- 멤버변수에 참조 타입이 있다면 해당 참조타입 또한 새로 만들어줘야한다.
이 변환 과정을 쉽게 보여주는 것이 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 |