일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 29 | 30 | 31 |
Tags
- 신입사원
- OS
- 프로그래밍
- 컴퓨터과학
- 우리카드
- 스프링
- 뮤텍스
- 알고리즘
- 신입
- 개발
- IT
- 자바
- spring
- CS
- 정보처리기사
- package-private
- 깃허브
- 운영체제
- java
- 이펙티브 자바
- Public
- 메모리
- Effective Java
- 세마포어
- 컴퓨터공학
- 스터디
- 디지털
- 깃
- github
- 공채
Archives
- Today
- Total
주니어 개발자 성장기
아이템 13. Clone 재정의는 주의해서 진행하라. 본문
구현 - 불변 객체
Cloenable
은 텅 비어 있는 인터페이스지만clone
을 이용하려면 반드시 구현(implements)해주어야 한다.- 그 다음
clone
메서드를super.clone
을 사용해 오버라이딩해줘야 한다. - 접근 제한자는 public 으로 설정, 반환 타입은 자신의 클래스로 변경한다.
Clone 규약
x.clone() != x
clone
반드시 원본과 다른 인스턴스를 반환해야 한다. (레퍼런스 자체가 달라야한다.)x.clone().getClass() == x.getClass()
가 반드시 true이여야 한다.
원본의 클래스와 동일한 인스턴스를 반환해야 한다.x.clone().equals(x)
가 true가 아닐 수도 있다.
복사를 한 인스턴스가 원본과 달라야만 한다면 true가 아닐수도 있다.
구현 - 가변 객체
- 불변 객체의 구현 과정을 동일하게 수행한다.
- 배열인 필드의 경우
clone
을 따로 호출해서 필드에 할당해주어야 한다. 문제는 이렇게 해도 배열의 원소들이 레퍼런스 타입이라면 배열자체는 복사가 되지만 배열의 원소는 Shallow Copy되어 존재하게 된다. 따라서 이런 경우 배열 내부 원소들까지 Deep Copy를 해줘야 한다.
유의할 점
- 자바는
Overriding
할 때, 리턴 타입을Overriding
대상이 되는 메서드의 리턴 타입의 하위 타입으로 지정해도Overriding
으로 인정된다. ← 타입 캐스팅을 하지 않아도 되는 장점이 있다.
이것을 공변환 이라고 하는데clone()
을Overriding
할 때 사용하면 좋다고 한다. Object.clone
은CloneNotSupportedException
라는 Checked Exception을 throw 하는데, 바람직한 설계라고 보기는 어렵다. 왜냐하면 해당 예외가 발생한다고 해서 할 수 있는 일이 없기 때문이다.
참고로 해당 예외는clone
이 호출된 클래스가Cloneable
을 구현(implement) 하지 않으면 발생한다.clone
을 구현할 때super.clone()
을 호출하지 않고 단순히 생성자를 이용할 수도 있다고 생각할 수도 있지만 반드시super.clone()
을 호출해주어야 한다.
왜냐하면 상속에서 문제가 발생하기 때문이다. 하위 타입에서clone
을 오버라이딩 할때 다음과 같이 구현할 수 있다.// Item.java public class Item implements Cloneable { private String name; @Override public Item clone() { Item item = new Item(); item.name = this.name; return item; } } // SubItem.java public class SubItem extends Item implements Cloneable { private String name; @Override public SubItem clone() { return (SubItem)super.clone(); }
위와 같이 두 클래스가 있을 때 clone
을 호출하면 어떻게 될까? ClassCastException
이 발생한다. 이유는 자바에서는 상위타입을 하위타입으로 Cast
할 수가 없기 때문이다.(반대는 가능하다.)
좀 더 구체적으로, Item
의 super.clone()
메서드는 Item
을 반환하는데, 이것을 SubItem
으로 캐스팅할 수 없기 때문에 ClassCastException
이 발생하는 것이다. 따라서 항상 super.clone()
을 호출해야 한다.
- 객체 생성에 관여하는 메서드들(이를테면 생성자,
clone
이 있다)은 하위 클래스에서 오버라이딩하지 못하도록final
private
으로 막거나 엄격한 룰을 적용하도록 강제해야 한다. 왜냐하면 하위 클래스에서 오버라이딩 하면 동작이 바뀔 가능성이 있기 때문이다. - 일반적으로 상속용 클래스에
Cloneable
인터페이스를 사용하지 않는 것이 좋다. 이렇게 하면clone
을 해당 클래스의 상속을 사용하려는 프로그래머에게 수 많은 고민을 안겨주기 때문이다. 그래서 방법이 2가지 있다.- 미리
clone
을 메서드로 정의한다. - 아예
protected final
이며 호출 시 Exception을 throw하는clone
메서드를 만들어 오버라이딩을 못하게 만들 수 있다.
- 미리
clone
을 구현할 때 객체 자체는super.clone
으로 복사를 하고 그 객체의 모든 필드는 그 필드의 클래스가 제공하는 외부로 노출된 (public) 고수준 API (이를테면Map.put
)를 통해서 데이터를 새로 만드는 방법이 있다. 안정적이긴 하나 저수준에 비해 다소 느릴 가능성도 있다. ← 대부분의 경우에 문제가 되지 않는다고 한다.- 만약에 Thread-safety를 보장해야 하는 클래스라면
synchronized
를 써야 한다.
실제
너무 사용하기 복잡해서 복사 전용 생성자나 정적 팩토리 메서드를 사용하는 것이 바람직하다.
- 생성자는 우리가 어떻게 동작하는 지 알지만
clone
은 내부 동작이 너무 불분명하다. - private final이면서 레퍼런스 타입인 필드의 경우
clone
으로 Deep copy가 불가능하다.
생성자의 정적 팩토리 메서드의 장점은 다음과 같다.
- 상위 타입(인터페이스 등)을 인자로 받아 변환하여 복사하는 것이 가능하며 이것을 변환 생성자(conversion constructor), 변환 팩터리(conversion factory)라고 한다.
- private final 필드까지 Deep copy가 가능하다.
결론
clone
은 사용법이 너무 복잡하다.- 복사 전용 생성자나 정적 팩토리 메서드를 사용하자
'Java > 이펙티브 자바' 카테고리의 다른 글
아이템 14. Comparable을 구현할지 고려하라 (0) | 2023.09.09 |
---|---|
방어적 복사, 얕은 복사, 깊은 복사 (0) | 2023.09.02 |
아이템 12. toString을 항상 재정의하라. (0) | 2023.09.02 |
아이템 11. equals를 재정의하려거든 hashCode도 재정의하라. (0) | 2023.09.01 |
아이템 10. equals는 일반 규약을 지켜 재정의하라 (0) | 2023.07.28 |