2023. 11. 24. 00:16ㆍBook/이펙티브 자바
- clone 규약
1. x.clone() != x 반드시 true
=> clone()이 반환한 객체와 원본은 반드시 달라야 한다.(reference가 달라야 한다.)
2. x.clone().getClass() == x.getClass() 반드시 true
=> clone()이 반환한 객체와 원본 객체의 클래스가 같아야 한다.
3. x.clone().equals(x) true가 아닐 수도 있다.
=> clone()이 반환한 객체가 새로 세팅이 필요한 경우가 있을 수도 있다.
// PhoneNumber에 clone 메서드 추가 (79쪽)
public final class PhoneNumber implements Cloneable {
private final short areaCode, prefix, lineNum;
public PhoneNumber(int areaCode, int prefix, int lineNum) {
this.areaCode = rangeCheck(areaCode, 999, "지역코드");
this.prefix = rangeCheck(prefix, 999, "프리픽스");
this.lineNum = rangeCheck(lineNum, 9999, "가입자 번호");
}
@Override public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof PhoneNumber))
return false;
PhoneNumber pn = (PhoneNumber)o;
return pn.lineNum == lineNum && pn.prefix == prefix
&& pn.areaCode == areaCode;
}
.
.
. 코드 생략
// 코드 13-1 가변 상태를 참조하지 않는 클래스용 clone 메서드 (79쪽)
@Override public PhoneNumber clone() {
try {
return (PhoneNumber) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError(); // 일어날 수 없는 일이다.
}
}
public static void main(String[] args) {
PhoneNumber pn = new PhoneNumber(707, 867, 5309);
Map<PhoneNumber, String> m = new HashMap<>();
m.put(pn, "제니");
PhoneNumber clone = pn.clone();
System.out.println(clone != pn);
System.out.println(clone.getClass() == pn.getClass());
System.out.println(clone.equals(pn));
}
}
=>
true
true
true
Cloneable은 복제해도 되는 클래스임을 명시하는 용도의 믹스인 인터페이스지만, 의도한 목적을 제대로 이루지 못했다.
가장 큰 문제는 clone 메서드가 선언된 곳이 Cloneable이 아닌 Object이고, 접근지시자도 protected라는 데 있다.
=> (다른 패키지나 상속 받은 객체만 사용한다는 보장도 없는데 말이다.)
그럼에도 불구하고 Cloneable방식은 널리 쓰이기에 잘 알아두는 것이 좋다고 한다.
Cloneable 인터페이스는 아무것도 없는데 대체 무슨 일을 할까?
=> 이 인터페이스는 Object의 protected 메서드인 clone의 동작 방식을 결정한다.
Cloneable을 구현한 클래스의 인스턴스에서 clone을 호출하면 그 객체의 필드들을 복사한 객체를 반환한다.
Cloneable을 구현하지 않은 클래스에서 clone을 호출하면 CloneNotSupportedException을 던진다.
/**
* Creates and returns a copy of this object. The precise meaning
* of "copy" may depend on the class of the object. The general
* intent is that, for any object {@code x}, the expression:
* <blockquote>
* <pre>
* x.clone() != x</pre></blockquote>
* will be true, and that the expression:
* <blockquote>
* <pre>
* x.clone().getClass() == x.getClass()</pre></blockquote>
* will be {@code true}, but these are not absolute requirements.
* While it is typically the case that:
* <blockquote>
* <pre>
* x.clone().equals(x)</pre></blockquote>
* will be {@code true}, this is not an absolute requirement.
* <p>
* By convention, the returned object should be obtained by calling
* {@code super.clone}. If a class and all of its superclasses (except
* {@code Object}) obey this convention, it will be the case that
* {@code x.clone().getClass() == x.getClass()}.
* <p>
* By convention, the object returned by this method should be independent
* of this object (which is being cloned). To achieve this independence,
* it may be necessary to modify one or more fields of the object returned
* by {@code super.clone} before returning it. Typically, this means
* copying any mutable objects that comprise the internal "deep structure"
* of the object being cloned and replacing the references to these
* objects with references to the copies. If a class contains only
* primitive fields or references to immutable objects, then it is usually
* the case that no fields in the object returned by {@code super.clone}
* need to be modified.
* <p>
* The method {@code clone} for class {@code Object} performs a
* specific cloning operation. First, if the class of this object does
* not implement the interface {@code Cloneable}, then a
* {@code CloneNotSupportedException} is thrown. Note that all arrays
* are considered to implement the interface {@code Cloneable} and that
* the return type of the {@code clone} method of an array type {@code T[]}
* is {@code T[]} where T is any reference or primitive type.
* Otherwise, this method creates a new instance of the class of this
* object and initializes all its fields with exactly the contents of
* the corresponding fields of this object, as if by assignment; the
* contents of the fields are not themselves cloned. Thus, this method
* performs a "shallow copy" of this object, not a "deep copy" operation.
* <p>
* The class {@code Object} does not itself implement the interface
* {@code Cloneable}, so calling the {@code clone} method on an object
* whose class is {@code Object} will result in throwing an
* exception at run time.
*
* @return a clone of this instance.
* @throws CloneNotSupportedException if the object's class does not
* support the {@code Cloneable} interface. Subclasses
* that override the {@code clone} method can also
* throw this exception to indicate that an instance cannot
* be cloned.
* @see java.lang.Cloneable
*/
@HotSpotIntrinsicCandidate
protected native Object clone() throws CloneNotSupportedException;
Object의 clone() 이 native이기 때문에 소스코드는 jvm.cpp에 있다.
clone 메서드를 보면 super.clone이 아닌 생성자(new PhoneNumber())를 호출해 인스턴스를 반환해도 정상적으로 작동할 것 처럼 보인다. 과연 그럴까?
public class Item implements Cloneable {
private String name;
/**
* 이렇게 구현하면 하위 클래스의 clone()이 깨질 수 있다.
*/
@Override
public Item clone() {
Item item = new Item();
item.name = this.name;
return item;
}
}
=======================================================================
public class SubItem extends Item implements Cloneable{
private String name;
@Override
public SubItem clone() {
//상위 타입을 구체 타입으로 캐스팅 못 함
return (SubItem) super.clone();
}
public static void main(String[] args) {
SubItem item = new SubItem();
SubItem clone = item.clone(); // => ClassCastException 발생
System.out.println(clone != null);
System.out.println(clone.getClass() == item.getClass());
System.out.println(clone.equals(item));
}
}
하위 클래스에서 상위 클래스의 super.clone을 호출한다면 ClassCastException이 발생해 규약을 지킬 수 없게 된다.
그러니 불변 객체라면 처음 코드처럼 다음 처럼만 하면 된다.
1. Cloneable인터페이스를 구현하고(안에 아무것도 없는 인터페이스)
2. clone 메서드를 재정의한다. 이대 super.clone()을 사용한다.
// 코드 13-1 가변 상태를 참조하지 않는 클래스용 clone 메서드 (79쪽)
@Override public PhoneNumber clone() {
try {
return (PhoneNumber) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError(); // 일어날 수 없는 일이다.
}
}
간단했던 위 구현이 가변객체를 참조하는 순간 심각한 에러를 발생시킬 수 있다.
'Book > 이펙티브 자바' 카테고리의 다른 글
Item14 - Comparable을 구현할지 고민하라 (1) | 2023.11.25 |
---|---|
Item13 - clone 재정의는 주의해서 진행하라(2) (1) | 2023.11.24 |
Item12 - toString을 항상 재정의하라 (1) | 2023.11.23 |
Item11 - equals를 재정의하려거든 hashCode도 재정의하라(2) (3) | 2023.11.22 |
Item11 - equals를 재정의하려거든 hashCode도 재정의하라 (0) | 2023.11.22 |