본문은 Effective Java를 읽고 간단하게 정리한 글입니다. 필요에 따라 생략/수정된 부분이 있을 수 있으며, 내용이 추후 변경될 수 있습니다.
선결론
- clone은 문제가 많은 메서드이다
- 반드시 써야 하는 상황이 아니라면 쓰지말고, 써야 한다면 주의해서 재정의하라
- 복사가 필요한 경우엔 clone 대신 복사 생성자와 복사 팩터리를 사용하라
- 그러나 유일하게 배열은 일반적으로 clone을 사용해도 된다
clone의 문제
- Unchecked Exception 핸들링 해줘야 함(try/catch)
- 허술한 규약 때문에 내부적으로 이상하게 재정의되어 원하는 대로 동작하지 않을 수 있음
- 예를 들어, 상위 클래스의 clone이 자신의 생성자를 호출하고 그 객체를 반환한다면 하위 클래스의 clone은 상위 클래스 객체를 반환하게 됨..
- 재정의를 잘못하면 NPE가 발생하거나 stackoverflow 등의 에러가 발생할 수 있음
그래서 어떻게?
재정의 하는 경우)
- 접근 제한자는 public으로, 반환 타입은 클래스 자신으로 변경
- super.clone을 호출한 후 필요한 필드를 전부 적절히 수정
- 기본 타입 필드 / 불변 객체 참조는 수정하지 않아도 괜찮지만, 고유 ID를 가진다면 해당 필드는 수정할 것
- 클래스 내부적으로 가변 객체나, 컬렉션에 담긴 가변 객체를 참조하고 있을 수 있다 -> 의도치 않은 참조가 일어날 수 있음
- 적절한 방법으로 이러한 객체들이 복사되도록 해야함
- 배열의 경우 clone을 사용할 것을 추천(개인적으론 예제와 상충되는 느낌..)
최선의 방법)
- 개인적으로 객체끼리 연관된 참조를 지니는 레퍼런스 타입 배열은 조심해야 하지 않을까 싶다(책에 나온 연결리스트처럼)
- 무조건 배열이니까 clone보다는 타입을 보고 결정하면 좋을듯 싶다..!!
- 복사 생성자/복사 팩터리 사용 (= 변환 생성자/변환 팩터리)
변환 생성자와 변환 팩터리에 대한 내용을 찾아봤는데 C++에서 쓰이는 개념이라 자바 버전으로 간단하게 예제코드를 작성했다.
Book.java
class Book {
private final String title;
private final String author;
private final Integer publishedYear;
public Book(String title, String author, Integer publishedYear) {
this.title = title;
this.author = author;
this.publishedYear = publishedYear;
}
public Book(Book book) {
this.title = book.title;
this.author = book.author;
this.publishedYear = book.publishedYear;
}
public static Book newBook(Book book) {
return new Book(book);
}
@Override
public String toString() {
return "Book{" +
"title='" + title + '\'' +
", author='" + author + '\'' +
", publishedYear=" + publishedYear +
'}';
}
}
public class Item13 {
public static void main(String[] args) {
Book book = new Book("아몬드", "손원평", 2017);
Book clonedBook1 = new Book(book);
Book clonedBook2 = Book.newBook(book);
System.out.println("book = " + book);
System.out.println("clonedBook1 = " + clonedBook1);
System.out.println("clonedBook2 = " + clonedBook2);
/**
* 출력결과
* book = Book{title='아몬드', author='손원평', publishedYear=2017}
* clonedBook1 = Book{title='아몬드', author='손원평', publishedYear=2017}
* clonedBook2 = Book{title='아몬드', author='손원평', publishedYear=2017}
*/
}
}
이런 식으로 생성자나 팩터리 메서드를 통해 전달받은 객체를 복사하라는 의미이다.
이는 Cloneable/clone 방식에서 발생했던 문제들(부가적인 형변환, 불필요한 예외처리, 위험한 생성 매커니즘)을 개선할 수 있는 방법이라고 한다.
참고로 책의 예제에서 HashSet 객체를 전달받아 TreeSet 타입으로 복제할 수 있다고 나왔는데, 실제 코드를 살펴보면 좀 더 이해가 수월하다.
// p.86 예제코드
new TreeSet<>(s)
TreeSet의 생성자는 Collection 타입의 객체 c를 인자로 전달받아 c의 모든 원소를 추가한 TreeSet타입의 객체를 반환한다.
HashSet은 Collection의 하위타입이기 때문에 예제와 같이 HashSet 객체를 생성자의 인자로 전달하여 TreeSet 타입으로 복제할 수 있는 것이다.
'책 > Effective Java' 카테고리의 다른 글
[이펙티브 자바] 아이템15: 클래스와 멤버의 접근 권한을 최소화하라 (0) | 2022.04.28 |
---|---|
[이펙티브 자바] 아이템14: Comparable을 구현할지 고려하라 (0) | 2022.04.27 |
[이펙티브 자바] 아이템12: ToString을 항상 재정의하라 (0) | 2022.04.26 |
[이펙티브 자바] 아이템11: equals를 재정의하려거든 hashCode도 재정의하라 (0) | 2022.04.22 |
[이펙티브 자바] 아이템10: equals는 일반 규약을 지켜 재정의하라 (0) | 2022.04.20 |