[F-lab 모각코 챌린지 19일차] TIL

JeongheeKim·2023년 6월 19일

TIL

목록 보기
19/66

객체 복사의이유

원본객체를 안전하게 보호하기 위함.

신뢰하지 않은 영역으로 객체를 전달할 경우 객체의 변경이 일어나 무결성이 깨질수 있기 때문에 신뢰하지 않은 영역으로 객체를 전달할때는 객체를 복사하여 전달하는것이 좋다.

Cloneable 인터페이스를 구현한 클래스의 인스턴스에서 clone메서드를 호출하면 그 객체의 필드들을 하나씩 복사한 객체를 반환한다.

→ 생성자를 통하지 않고 객체를 생성할 수 있게되어 기존 매커니즘과 모순된다.

clone()

  • 객체를 복사하는 역할을 하는 메소드 이며, Object 클래스에 정의되었다.
  • Cloneable 인터페이스를 구현하여 jvm에게 복제 가능한 객체임을 알려줘야한다. 만약 인터페이스를 구현하지 않으면 CloneNotSupportedException이 발생한다.
  • clone() 메소드는 protected 권한이므로 복제할 클래스에서 public으로 재정의하여 어디서나 객체를 복사할 수 있게 해야 한다.
protected native Object clone() throws CloneNotSupportedException;

아래 예시를 보면 객체를 복사하는 두가지 방식에 대해 확인할 수 있다. 깊은 복사 & 얕은 복사에 대해 알아보자.

public class Student implements Cloneable{
	private String name;
	private int grade;

	public Student(String name, int grade) {
		this.name = name;
		this.grade = grade;
	}

	@Override
	public Object clone() throws CloneNotSupportedException {
		return super.clone();
	}
}
public class Main {
	public static void main(String[] args) {
		try {
			//shallow
			Student student1 = new Student("ken", 1);
			Student student1_copy = student1;

			System.out.println("student1 = " + student1);
			System.out.println("student1_copy = " + student1_copy);

			//deep
			Student student2 = new Student("ken", 1);
			Student student2_copy = (Student)student2.clone();

			System.out.println("student2 = " + student2);
			System.out.println("student2_copy = " + student2_copy);

		} catch (CloneNotSupportedException e) {
			throw new RuntimeException(e);
		}
	}
}
student1 = Student@59a6e353
student1_copy = Student@59a6e353

student2 = Student@7a0ac6e3
student2_copy = Student@71be98f5
  • 얕은 복사(shallow copy)
    • 필드값을 복사하여 객체를 복사하는것을 뜻한다.
      - 기본 타입의 경우 값의 복사, 참조자료형인 경우 객체의 참조값이 복사된다.
      - 참조 자료형의 경우 참조값 복사로 인해 동일한 heap영역의 데이터를 보게된다.

      ```java
      public class ShallowCopyEx {
      	public static void main(String[] args) {
      		Member member1 = new Member();
      		Member member2 = member1;
      
      		System.out.println("member1 = " + member1);
      		System.out.println("member2 = " + member2);
      		System.out.println("======================================");
      
      		member2.setName("ellen");
      		member2.setNum(50);
      
      		System.out.println("member1 = " + member1);
      		System.out.println("member2 = " + member2);
      		
      	}
      }
      ```
      
      ```java
      member1 = Member{name='smith', num=30}
      member2 = Member{name='smith', num=30}
      ======================================
      member1 = Member{name='ellen', num=50}
      member2 = Member{name='ellen', num=50}
      ```

      테스트 결과를 보면 객체의 참조값이 복사되어 값 변경 전후로 member1, member2의 필드값이 동일한것을 확인할 수 있다.

  • 깊은 복사(deep copy)
    • 원본 객체가 참조하고있는 힙의 데이터까지 복제되어 원본의 변경이 복사본 객체에 영향을 주지 않는다.

    • 실제 값을 새로운 메모리 공간에 복사되며, 참조값이 복사되지 않는다.

    • clone()를 재정의하여 복사하는 방식이 깊은복사에 해당한다.

    • 객체 형태에 따른 복사
      - 참조자료형을 담고있는 객체의 배열의 복사일 경우 clone메소드 재정의를 해야 완벽한 복사가 이루어진다.

      public class Main {
      	public static void main(String[] args) {
      
      		Student[] arr = {
      			new Student("ken", 1),
      			new Student("min", 2),
      			new Student("roy", 3)
      		};
      
      		System.out.println(Arrays.toString(arr));
      
      		Student[] copyArr;
      		copyArr = arr.clone();
      
      		System.out.println(Arrays.toString(copyArr));
      
      		System.out.println("copyArr[0].getGrade() = " + copyArr[0].getGrade());
      		copyArr[0].setGrade(5);
      		System.out.println("arr[0].getGrade() = " + arr[0].getGrade());
      		System.out.println("copyArr[0].getGrade() = " + copyArr[0].getGrade());
      
      	}
      }

      배열에 대해 clone메소드로 복사했지만 배열 요소는 다 동일한 참조값을 가지는것을 확인할 수 있다. 또한 배열의 요소값이 원본 배열의 요소와 복사한 배열의 요소의 값이 동일하게 변경된것을 확인할 수 있다.

      [Student@7d417077, Student@7dc36524, Student@35bbe5e8]
      [Student@7d417077, Student@7dc36524, Student@35bbe5e8]
      
      copyArr[0].getGrade() = 1
      arr[0].getGrade() = 5
      copyArr[0].getGrade() = 5

      이를 해결하기 위해 배열의 요소를 for문을 돌면서 clone으로 복사해야 한다.

      public class Mai {
      	public static void main(String[] args) throws CloneNotSupportedException {
      
      		Student[] arr = {
      			new Student("ken", 1),
      			new Student("min", 2),
      			new Student("roy", 3)
      		};
      
      		System.out.println(Arrays.toString(arr));
      
      		Student[] copyArr = new Student[arr.length];
      		for (int i = 0; i < arr.length; i++) {
      			copyArr[i] = (Student)arr[i].clone();
      		}
      
      		System.out.println(Arrays.toString(copyArr));
      
      		System.out.println("copyArr[0].getGrade() = " + copyArr[0].getGrade());
      		copyArr[0].setGrade(5);
      		System.out.println("========= after set value ==============");
      		System.out.println("arr[0].getGrade() = " + arr[0].getGrade());
      		System.out.println("copyArr[0].getGrade() = " + copyArr[0].getGrade());
      
      	}
      }
      [Student@7d417077, Student@7dc36524, Student@35bbe5e8]
      [Student@2c8d66b2, Student@5a39699c, Student@3cb5cdba]
      copyArr[0].getGrade() = 1
      ========= after set value ==============
      arr[0].getGrade() = 1
      copyArr[0].getGrade() = 5

      [참고]

      https://inpa.tistory.com/entry/JAVA-☕-Object-클래스-clone-메서드-얕은-복사-깊은-복사#얕은복사_vs깊은_복사

      https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#clone--

      https://edu.goorm.io/learn/lecture/41/바로실습-생활코딩-자바-java/lesson/39127/clone

0개의 댓글