public class Person {
String name;
int age;
Person (String name, int age) {
this.name = name;
this.age = age;
}
}
/**
* A class implements the Cloneable interface to indicate to the Object.clone() method that it is legal for that method to make a field-for-field copy of instances of that class.
Invoking Object's clone method on an instance that does not implement the Cloneable interface results in the exception CloneNotSupportedException being thrown.
By convention, classes that implement this interface should override Object.clone (which is protected) with a public method. See Object.clone() for details on overriding this method.
Note that this interface does not contain the clone method. Therefore, it is not possible to clone an object merely by virtue of the fact that it implements this interface. Even if the clone method is invoked reflectively, there is no guarantee that it will succeed.
*/
public interface Cloneable {
}
public class Object {
// ...
@HotSpotIntrinsicCandidate
protected native Object clone() throws CloneNotSupportedException;
// ...
}
clone 메서드는 객체를 복제하여 새로운 객체를 반환하는 메서드
- protected
- 같은 패키지나
자식 클래스
에서 사용할 수 있는 접근 제어자- native
- JNI(Java Native Interface)를 사용하여 실행하는 메서드
- 동작
Cloneable
을 구현했는지 확인 → 안되었으면CloneNotSupportedException
발생- 객체를 복제한 새 객체를 반환(구현은 JVM마다 다를 수 있다)
- 규약
- x.clone() != x : 새로운 객체여야 한다.
- x.clone().getClass() == x.getClass() : 같은 타입(클래스)여야 한다.
- x.clone().equals(x) : 필드가 모두 같아야 한다.
@Test
public void test() {
ChessPiece chessPiece = new ChessPiece();
try {
chessPiece.clone();
System.out.println("예외 발생하지 않음");
} catch (CloneNotSupportedException e) {
System.out.println("예외 발생함");
}
}
public class ChessPiece {
// ...
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
// ...
}
public class ChessPiece implements Cloneable {
// ...
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
// ...
}
class A implements Cloneable {
private final int value;
private final String text;
public A(int value, String text) {
this.value = value;
this.text = text;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
A a = (A) o;
return value == a.value && Objects.equals(text, a.text);
}
@Override
public int hashCode() {
return Objects.hash(value, text);
}
}
@Test
public void test() {
try {
A a = new A(1, "일");
A clone = (A) a.clone();
System.out.println(a != clone);
System.out.println(a.getClass() == clone.getClass());
System.out.println(a.equals(clone));
} catch (CloneNotSupportedException e) {
System.out.println("예외 발생함");
}
}
@Override
public Object clone() throws CloneNotSupportedException {
System.out.println("a.clone() 호출");
return new A(this.value, this.text);
}
super.clone()
으로 새로운 객체를 복제하여 반환하는 것 @Override
public Object clone() throws CloneNotSupportedException {
System.out.println("a.clone() 호출");
return super.clone();
}
A의 입장에서는 두 경우가 별반 차이가 없다.
만약, A를 상속한 B가 존재한다면
class B extends A {
public B(int value, String text) {
super(value, text);
}
@Override
protected Object clone() throws CloneNotSupportedException {
System.out.println("b.clone() 호출");
return super.clone();
}
}
@Test
public void test() {
try {
B b = new B(1, "일");
Object clone = b.clone();
System.out.println("b의 타입 : " + b.getClass());
System.out.println("clone의 타입 : " + clone.getClass());
} catch (CloneNotSupportedException e) {
System.out.println("예외 발생함");
}
}
public class ChessPiece implements Cloneable {
private final Role role; //enum
private final Color color; //enum
@Override
public ChessPiece clone() throws CloneNotSupportedException {
return (ChessPiece) super.clone();
}
// ...
}
불변 객체를 복제할 필요가 있을까?
public class Board implements Cloneable{
private final Map<Square, Piece> board;
private Side turn;
// ...
@Override
public Board clone() throws CloneNotSupportedException {
return (Board) super.clone();
}
}
Board board
와 board.clone()
은 다른 객체지만, 필드의 참조주소는 같다.
board.clone()
의 상태의 변화가board
의 상태를 변화하게 된다. @Override
public Board clone() throws CloneNotSupportedException {
Board result = (Board) super.clone();
result.board = board.clone();
result.side = side.clone();
return result;
}
public class HashTable implements Cloneable {
private Entry[] buckets = new Entry[10];
private static class Entry {
final Object key;
Object value;
Entry next;
Entry(Object key, Object value, Entry next) {
this.key = kye;
this.value = value;
this.next = next;
}
}
@Override
public HashTable clone() {
try {
HashTable result = (HashTable) super.clone();
result.buckets = buckets.clone();
return result;
} catch (CloneNotSupportedException e) {
// ...
}
}
}
public class HashTable implements Cloneable {
private Entry[] buckets = new Entry[10];
private static class Entry {
final Object key;
Object value;
Entry next;
Entry(Object key, Object value, Entry next) {
this.key = kye;
this.value = value;
this.next = next;
}
Entry deepCopy() {
//return new Entry(key, value, next == null ? null : next.deepCopy());
Entry result = new Entry (key, value, next);
for (Entry p = result; p.next != null; p = p.next) {
p.next = new Entry(p.next.key, p.next.value, p.next.next);
}
}
}
@Override
public HashTable clone() {
try {
HashTable result = (HashTable) super.clone();
result.buckets = new Entry[buckets.length];
for (int i = 0; i < buckets.length; i++) {
result.buckets[i] = this.buckets[i] == null ?
null : this.buckets[i].deepCopy();
}
return result;
} catch (CloneNotSupportedException e) {
// ...
}
}
}
@Override
public HashTable clone() {
try {
HashTable result = (HashTable) super.clone();
result.buckets = new Entry[buckets.length];
for (int i = 0; i < buckets.length; i++) {
result.put(key, value);
}
return result;
} catch (CloneNotSupportedException e) {
// ...
}
}
재정의될 가능성이 있는 메서드는 호출하지 않아야한다.
throws 절을 없앤다.
clone() 퇴화
public ChessPiece(ChessPiece chesspiece) {
// ...
}
public static ChessPiece copyOf(ChessPiece chesspiece) {
// ..
return ...;
}