티스토리 뷰

java

얕은 복사와 깊은 복사

주다애 2024. 11. 27. 13:50

🖨 자바에서의 객체 복사

자바에서의 객체 복사는 얕은 복사와 깊은 복사가 존재한다.

 

둘의 차이를 알아보자

사용할 예제는 다음과 같다.

 

import java.util.Objects;

class Book {

	private String name; // 책 이름
	private Author author; // 저자

	public Book(String name, Author author) {
		this.name = name;
		this.author = author;
	}

	public Book shallowCopy() { // 얕은 복사
		return new Book(this.name, this.author);
	}

	public Book deepCopy() { // 깊은 복사
		Author copiedAuthor = new Author(this.author.getName());
		return new Book(this.name, copiedAuthor);
	}

	public void changeAuthor(String name) { // 저자 이름 변경
		author.setName(name);
	}

	@Override
	public String toString() {
		return "Book name : " + name + ", " + author;
	}

	static class Author {

		private String name; // 저자 이름

		public Author(String name) {
			this.name = name;
		}

		public String getName() { // 저자 이름 반환
			return name;
		}

		public void setName(String name) { // 저자 이름 변경
			this.name = name;
		}

		@Override
		public String toString() {
			return "Author : " + name;
		}
	}

	@Override
	public boolean equals(Object o) {
		if (this == o)
			return true;
		if (o == null || getClass() != o.getClass())
			return false;
		Book book = (Book)o;
		return Objects.equals(name, book.name) && Objects.equals(author, book.author);
	}

	@Override
	public int hashCode() {
		return Objects.hash(name, author);
	}

	public static void main(String[] args) {
		Author author1 = new Author("조슈아 블로크");
		Book book1 = new Book("이펙티브 자바", author1);

		// 얕은 복사 후 변경
		Book shallowCopyBook = book1.shallowCopy();
		shallowCopyBook.changeAuthor("Joshua Bloch");

		// 얕은 복사 결과 출력
		System.out.println("After shallow copy and change:");
		System.out.println("Original book1: " + book1);
		System.out.println("Shallow copied book: " + shallowCopyBook);

		System.out.println("book1's hashcode: " + book1.hashCode());
		System.out.println("shallowCopyBook's hashcode: " + shallowCopyBook.hashCode());
		System.out.println("book1's author's hashcode: " + book1.author.hashCode());
		System.out.println("shallowCopyBook's  author's hashcode: " + shallowCopyBook.author.hashCode() + "\n");

		Author author2 = new Author("마틴 파울러");
		Book book2 = new Book("리팩터링", author2);

		// 깊은 복사 후 변경
		Book deepCopyBook = book2.deepCopy();
		deepCopyBook.changeAuthor("Martin Fowler");

		// 깊은 복사 결과 출력
		System.out.println("After deep copy and change:");
		System.out.println("Original book2: " + book2);
		System.out.println("Deep copied book: " + deepCopyBook);

		System.out.println("book2's hashcode: " + book2.hashCode());
		System.out.println("deepCopyBook's hashcode: " + deepCopyBook.hashCode());
		System.out.println("book2's author's hashcode: " + book2.author.hashCode());
		System.out.println("deepCopyBook's  author's hashcode: " + deepCopyBook.author.hashCode());
	}
}

 

조금은 복잡해 보이지만 프린트 문이 길어서 그렇다.

하나씩 살펴보자

 

얕은 복사

자바의 얕은 복사는 복사 대상 객체의 참조(주소값)만 복사한다.

 

즉, 원본과 복사본이 같은 힙(Heap)의 데이터를 바라보는 것이다.(객체를 생성하면 힙 영역에 객체의 값이 올라간다.)

public Book shallowCopy() { // 얕은 복사
    return new Book(this.name, this.author);
}

 

여기서 Book 객체는 new를 통해 새로 만들어 주었지만 Author 객체는 기존의 값과 같은 참조를 가진다.

shallowCopy()를 사용해서 얕은 복사를 실행하면

Author author1 = new Author("조슈아 블로크");
Book book1 = new Book("이펙티브 자바", author1);

// 얕은 복사 후 변경
Book shallowCopyBook = book1.shallowCopy();
shallowCopyBook.changeAuthor("Joshua Bloch");

 

book1 객체와 shallowCopyBook 객체가 같은 Author 참조값을 가지게되는 것이다.

그래서 shallowCopyBook에서 changeAuthor() 메소드를 통해 저자 이름을 바꾸게 되면 book1 객체에도 영향을 준다.

둘은 같은 Author 객체를 공유하기 때문이다.

 

결과 값을 출력하면

둘의 저자 이름이 모두 같은 값으로 변경됐다.

이렇게 Author 값이 둘 다 변경된 것을 알 수 있다.

 

equals()와 hashCode() 메소드를 오버라이딩해서 해시 코드 값이 같은지도 살펴보면

같은 해시 코드를 가진다.

둘의 Author 객체는 같은 해시 코드 값을 가짐을 알 수 있다. 

동일성 비교를 해도

System.out.println(book1.author == shallowCopyBook.author);

같음을 확인할 수 있다.

 

깊은 복사

자바의 깊은 복사는 원본이 참조하고 있는 힙의 데이터까지 복사한다.

 

즉, 원본과 복사본이 서로 다른 객체를 참조하므로 원본의 변경이 복사본의 변경에 영향을 주지 않는다.

public Book deepCopy() { // 깊은 복사
    Author copiedAuthor = new Author(this.author.getName());
    return new Book(this.name, copiedAuthor);
}

 

deepCopy() 메소드를 살펴보면 new를 통해 새로운 Author 객체(copiedAuthor)를 생성하고 그 후 new를 통해 새로운 Book 객체를 리턴해주고 있다.

그래서  deepCopy() 를 통해 deepCopyBook 객체를 만들고 changeAuthor() 메소드를 통해 저자 이름을 바꾸게 되면 book2 객체에 영향을 주지 않는다.

애초에 각각 다른 Author 주소값을 가리키고 있기 때문이다.

 

결과 값을 출력하면

저자 이름이 복사본만 변경됐다.

이렇게 변경을 한 복사본의 저자 이름만 변경된 것을 알 수 있다.

 

equals()와 hashCode() 메소드를 오버라이딩해서 해시 코드 값이 다른지도 살펴보면

다른 해시 코드를 가진다.

둘의 Author 객체는 다른 해시 코드 값을 가짐을 알 수 있다. 

동일성 비교를 해도 당연히 false를 리턴한다.

 

clone 메소드

Object.clone() 메소드는 인스턴스 객체의 복제를 위한 메소드로 해당 인스턴스를 복제해서 새로운 인스턴스를 생성하여 그 참조값을 반환한다.

 

clone() 메소드를 사용하기 위해서는 Cloneable 인터페이스를 구현해야 한다.(implements Cloneable)

그 후 해당 클래스에서 clone() 메소드를 오버라이딩 하면 된다.

예를 들어서 Book 클래스가 Cloneable 인터페이스를 구현하고 그 안에 clone() 메소드가 오버라이딩 되어있다고 하면

// 깊은 복사(deep copy)
Book book = new Book();
book.setName("책책");
Book copiedBook = (Book) book.clone();

 

book 객체와 copiedBook은 다른 힙 영역을 참조한다. 둘은 당연히 다른 해시 코드를 가진다.

 

기억해야 할 점은

데이터 clone의 핵심은 원시 타입이 아닌 클래스를 복사할 일이 있을 경우 clone() 메소드를 꼭 오버라이딩 해야한다는 것이다. 그래야 깊은 복사를 수행할 수 있다.

 

다루는 데이터가 참조 타입인 경우 clone() 메소드를 재정의해야 깊은 복사가 이루어진다.

 

참고 자료

https://inpa.tistory.com/entry/JAVA-%E2%98%95-Object-%ED%81%B4%EB%9E%98%EC%8A%A4-clone-%EB%A9%94%EC%84%9C%EB%93%9C-%EC%96%95%EC%9D%80-%EB%B3%B5%EC%82%AC-%EA%B9%8A%EC%9D%80-%EB%B3%B5%EC%82%AC

 

☕ 자바 clone 메서드 재정의 (얕은 복사 & 깊은 복사)

clone 메소드 Object.clone() 메소드는 인스턴스 객체의 복제를 위한 메소드로, 해당 인스턴스를 복제하여 새로운 인스턴스를 생성해 그 참조값을 반환한다. 이러한 clone() 메소드를 사용하기 위해서

inpa.tistory.com

 

'java' 카테고리의 다른 글

동일성과 동등성  (0) 2024.12.03
일급 컬렉션  (0) 2024.11.23
자바의 Checked Exception VS Unchecked Exception  (1) 2024.11.21
인터페이스 vs 추상클래스  (0) 2024.05.16
상속과 메모리 구조  (0) 2023.12.21
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/02   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28
글 보관함