Spring, CS 공부 내용 7

김정용·2025년 1월 27일

기술면접 공부

목록 보기
7/15
post-thumbnail

얕은복사와 깊은복사

이 포스트에서는 자바의 얕은복사, 깊은복사에 대한 내용을 다룬다.

Book이라는 클래스가 있다고 가정할때, 얕은 복사와 깊은 복사를 살펴보자.

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

로직만 살펴보자

해당 복사의 동작들은 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;
        }
    }

얕은 복사

얕은 복사의 경우 새로운 Book 객체를 생성하고 현재 Book의 name, author를 받게된다.

Book 객체는 새로 생성하지만 내부에 있는 Author 객체의 경우 원래 사용하는 Author 객체를 가져와 사용하게 된다. (참조의 개념)

다시 말하면 복사라기보다 참조한다는게 중요하다.

예시

book1 객체를 생성하고 얕은 복사를 거친 후에 값을 변경해본다.

        Author author1 = new Author("조슈아 블로크");
        Book book1 = new Book("이펙티브 자바", author1);
        Book shallowCopyBook = book1.shallowCopy();
        shallowCopyBook.changeAuthor("Joshua Bloch");

조슈아 블로크 라는 이름의 저자를 Author 객체 생성을 통해 생성하고 이 객체를 저자로한 Book 객체인 이펙티브 자바 책을 생성하는 과정을 나타낸다.

line 3에서 shallowCopyBook(얕은 복사)를 하고 내부적으로

return new Book(this.name, this.author);

새로운 Book 객체인 shallowCopyBook를 반환한다.

line 4에서 changeAuthor 메소드를 통해 Author의 정보를 변경하고 있다.

결과

어떤 결과가 나올까?

Book은 새로운 객체를 사용했지만 Author는 기존 객체를 참조하였기 때문에 shallowCopyBook의 저자이름을 변경하게 되면 book1의 저자 이름도 Joshua Bloch로 변경된다.

얕은 복사는 하나의 객체를 공유하고 있다고 생각하면 된다.

(자바에서 얕은 복사는 C에서 포인터와 비슷하게 동작한다.)


깊은 복사

깊은 복사는 어떻게 다른지 살펴보자

예시

        Author author1 = new Author("조슈아 블로크");
        Book book1 = new Book("이펙티브 자바", author1);
        Book deepCopyBook = book2.deepCopy();
        deepCopyBook.changeAuthor("Martin Fowler");

깊은 복사도 main 메소드에서 로직은 비슷하게 생겼지만 deepCopy() 메소드 내부에서는

Author copiedAuthor = new Author(this.author.getName());
return new Book(this.name, copiedAuthor);

Author 객체를 먼저 새로 만들고 Book에 넣어주는 것을 알 수 있다.

이후 복사한 Book 객체인 deepCopyBook의 저자를 Martin Fowler로 변경한다.

결과적으로 deepCopyBook이라는 깊은 복사가 수행된 Book 객체가 생성된다.

결과

얕은 복사와 다를까?

깊은 복사는 얕은 복사와 다르게 새로운 Author 객체를 생성했기 때문에 deepCopyBook의 저자 이름을 바꾸었다고 하더라도 기존 book2의 저자 이름은 바뀌지 않는다.

쉽게 말하면 각자 다른 객체를 참조하고 있다.


얕고 깊음의 유불리

그렇다면 깊은 복사가 무조건적으로 더 유리한 방법일까?

정답은 No 이다.

각각의 방법에서 유불리가 존재하는데 어떤 점인지 확인해보자.

얕은 복사의 유불리

얕은 복사는 기존 객체의 필드를 그대로 참조하여 사용하기 때문에 메모리 사용량이나 복사하는데에 시간을 줄일 수 있다.

객체의 크기가 매우 크거나 중첩된 객체를 포함하고 있을때는 깊은복사의 사용이 성능 저하를 가져올 수 있다.

또한, 복사한 객체와 원본 객체간 변경사항이 없는 경우 얕은 복사를 통해서 참조를 공유하는 방법을 사용할 수 있다.

하지만 참조를 공유하고 변경사항 또한 반영되기때문에 객체 간 독립성이 중요한 경우 사용 시 문제가 발생 할 수 있다.

깊은 복사의 유불리

얕은 복사의 장단점이 깊은 복사의 장단점과 반대라고 생각하면된다.

내부의 필드를 복사하는 과정이 필요한 깊은 복사의 경우 얕은 복사보다 구현하는데에 복잡성이 증가할 수 있다.

반대로 객체간 완전히 독립된 동작을 원한다면 깊은 복사를 하는게 필수적인 부분이다.


Cloneable - 비권장

얕은 복사, 깊은 복사를 공부하다가 Cloneable이라는 인터페이스를 알게 되었다.

java에서 인스턴스를 복사하는데 도움을 주는 clone() 메소드가 존재한다.

이 clone 메소드를 사용할때, 필수적인게 Cloneable 인터페이스를 구현하는 것이다.

class Book implements Cloneable {
    private String title;
    private String author;

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

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone(); // 얕은 복사 수행
    }
}

다음과 같이 복사가 수행될 클래스에 Cloneable 인터페이스를 붙이고 내부 clone() 메소드를 오버라이드해서 사용한다.

clone()은 기본적으로 얕은 복사를 수행하게 되고 깊은 복사는

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Book cloned = (Book) super.clone(); // 얕은 복사 수행
        cloned.author = new Author(this.author.getName()); // 깊은 복사 수행
        return cloned;
    }

다음과 같이 진행하면 된다.


Builder

Cloneable의 경우 구현의 복잡성과 인터페이스 설계의 비직관적인 특징으로 여러 대안이 권장된다.

그 중에서 우리가 Springboot 프레임워크에서 자주 사용하는게 Builder 패턴이다.

Builder 패턴을 사용하면 객체를 쉽게 복사 및 사용할 수 있다.


  • 얕은 복사
Book book1 = new Book.Builder()
        .title("Effective Java")
        .author(new Author("Joshua Bloch"))
        .build();

Book shallowCopy = new Book.Builder()
        .title(book1.toString())
        .author(book1.author) // 참조를 복사
        .build();

  • 깊은 복사
Book book1 = new Book.Builder()
        .title("Effective Java")
        .author(new Author("Joshua Bloch"))
        .build();
        
Book deepCopy = new Book.Builder()
        .title(book1.toString())
        .author(new Author(book1.author.getName())) // 새로운 객체 생성
        .build();

참고

면접 질문 내용과 답변의 일부는 기술 면접 구독 서비스 - 매일메일 에 있다.
흥미로웠다면 구독해보는 것도 추천한다!

profile
누군가의 롤모델이 될 때까지😇

0개의 댓글