Cloneable 인터페이스는 복제해도 되는 클래스임을 명시하는 용도의 믹스인 인터페이스지만, 아쉽게도 의도한 목적을 제대로 이루지 못했다. 여기서 큰 문제점은 clone 메서드가 선언된 곳이 Cloneable이 아닌 OBject이고, 그 마저도 protected이다. 그래서 Cloneable을 구현하는 것만으로는 외부 객체에서 clone 메소드를 호출할 수 없다. 리플렉션을 사용하면 가능하지만, 100% 성공하는 것도 아니다.
이러한 여러 문제점을 가진 인터페이스이지만, Cloneable 방식은 널리 쓰이고 있어서 잘 알아두는 것이 좋다.
Cloneable이 몰고 온 모든 문제를 되짚어봤을 때, 새로운 인터페이스를 만들 때는절대 Cloneable을 확장해서는 안 되며, 새로운 클래스도 이를 구현해서는 안된다. final 클래스라면 Cloneable을 구현해도 위험이 크지는 않지만, 성능 최적화 관점에서 검토한 후 별다른 문제가 없을 때만 드물게 허용해야 한다.
기본 원칙은 '복제 기능은 생성자와 팩터리를 이용하는게 최고' 라는 것이다.
단,배열만은 clone 메소드 방식이 가장 깔끔한, 이 규칙의 합당한 예외라 할 수 있다.
public class CopyObject {
private String name;
private int age;
public CopyObject() {
}
/* 복사 생성자 */
public CopyObject(CopyObject original) {
this.name = original.name;
this.age = original.age;
}
/* 복사 팩터리 */
public static CopyObject copy(CopyObject original) {
CopyObject copy = new CopyObject();
copy.name = original.name;
copy.age = original.age;
return copy;
}
public CopyObject(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
@Test
void shallowCopy() {
CopyObject original = new CopyObject("JuHyun", 20);
CopyObject copyConstructor = new CopyObject(original);
CopyObject copyFactory = CopyObject.copy(original);
copyConstructor.setName("JuBal");
copyFactory.setName("BalJu");
System.out.println(original.getName());
System.out.println(copyConstructor.getName());
System.out.println(copyFactory.getName());
}
복사 생성자와 복사 팩터리를 통해 객체를 복사하는 과정도 깊은 복사임을 알 수 있습니다.
깊은 복사를 그림으로 나타내면 다음과 같습니다.
얕은 복사와는 다르게 Heap 영역에 새로운 메모리 공간을 생성하여 실제 값을 복사합니다.
Collections나 Map의 경우 이미 복사 팩터리인 copy() 메소드를 구현하고 있습니다.
/**
* Copies all of the elements from one list into another. After the
* operation, the index of each copied element in the destination list
* will be identical to its index in the source list. The destination
* list's size must be greater than or equal to the source list's size.
* If it is greater, the remaining elements in the destination list are
* unaffected. <p>
*
* This method runs in linear time.
*
* @param <T> the class of the objects in the lists
* @param dest The destination list.
* @param src The source list.
* @throws IndexOutOfBoundsException if the destination list is too small
* to contain the entire source List.
* @throws UnsupportedOperationException if the destination list's
* list-iterator does not support the {@code set} operation.
*/
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
int srcSize = src.size();
if (srcSize > dest.size())
throw new IndexOutOfBoundsException("Source does not fit in dest");
if (srcSize < COPY_THRESHOLD ||
(src instanceof RandomAccess && dest instanceof RandomAccess)) {
for (int i=0; i<srcSize; i++)
dest.set(i, src.get(i));
} else {
ListIterator<? super T> di=dest.listIterator();
ListIterator<? extends T> si=src.listIterator();
for (int i=0; i<srcSize; i++) {
di.next();
di.set(si.next());
}
}
}
1. 제어문(control statement)
- 프로그램의 흐름(flow)을 바꾸는 역할을 하는 문장
-조건문: 조건에 따라 다른 문장이 수행되도록 함
-반복문: 특정 문장들을 반복해서 수행
2. 조건문 - if, switch
1) if문
if (조건식) { // 조건식이 참(true)일 때 수행될 문장 }
-만일(if) 조건식이 참(true)이면 괄호 { } 안의 문장들을 수행하라
- 조건식은 일반적으로비교연산자와논리연산자로구성 (반드시 true 혹은 false의 값을 가져야 함)
-블럭(block): 괄호 '{ }'를 이용해서 여러 문장을 하나의 단위로 묶음
[잘못된 예시] if(score > 60) System.out.println("합격"); // if문에 속한 문장 System.out.println("축하"); // if문에 속하지 않은 문장
[올바른 예시] if(score > 60) { System.out.println("합격"); // if문에 속한 문장 System.out.println("축하"); // if문에 속한 문장 }
-> 블럭 내의 문장이 하나라면 괄호를 생략할 수 있지만, 여러 문장일 경우 꼭 괄호로 묶어줘야 함
2) if-else문
if (조건식) { // 조건식이 참(true)일 때 수행될 문장 } else { // 조건식이 거짓(false)일 때 수행될 문장 }
3) if-else if문
if (조건식1) { // 조건식1이 참(true)일 때 수행될 문장 } else if (조건식2) { // 조건식2이 참(true)일 때 수행될 문장 } else if (조건식3) { // 조건식3이 참(true)일 때 수행될 문장 } else { // 조건식1,2,3이 모두 거짓(false)일 때 수행될 문장 }
4) 중첩 if문
if (조건식1) { // 조건식1이 참(true)일 때 수행될 문장 if (조건식2) { // 조건식1이 true면서 조건식2가 true일 때 수행될 문장 } else { // 조건식1이 true면서 조건식2가 false일 때 수행될 문장 } } else { // 조건식1이 거짓(false)일 때 수행될 문장 }
5) swtich문
switch (조건식) { case 값1 : // 조건식의 결과가 값1과 같을 경우 수행될 문장 break; case 값2 : // 조건식의 결과가 값2와 같을 경우 수행될 문장 break; default : // 조건식의 결과와 일치하는 case문이 없을 때 수행될 문장들 }
- 조건식의 결과는정수, 문자열만 가능
- case문의 값은정수, 상수만 가능하며중복 불가능 (변수, 실수 불가능!)
- 조건식의 결과와 일치하는 case문이 하나도 없는 경우 default문으로 이동
-break문을 사용하지 않으면 다른 break문을 만나거나 switch문 블럭의 끝을 만날 때까지 나오는 모든 문장들을 수행하므로 의도한게 아니라면 case문 끝에 break를 써주는 것을 잊지 말아야 함 (맨 마지막은 생략 가능)
- switch문도 if문처럼 중첩이 가능함
3. 반복문 - for, while, do-while
1) for문
- 주로 반복횟수를 알고 있을 때 사용
for(inti=1;i<=5;i++) { // 반복 수행할 문장 } 1)초기값: 반복문에 사용될 변수를 초기화하며 처음 한번만 수행, 둘 이상의 변수 선언 가능 (타입이 같아야 함) ex) for(int i=1, j=0; i<=5; i++) { ... } 2)조건식: 조건식의 값이 참(true)이면 반복을 계속하고, 거짓(false)이면 반복을 중단하고 for문을 벗어남 3)증감식: 반복문을 제어하는 변수이 값을 증가 또는 감소시킴
-> 세 요소는 필요하지 않으면 생략할 수 있으며, 모두 생략도 가능함 ex) for(;;) { ... }
2) 중첩 for문
- for문안에 for문을 포함시킬 수 있음
for(int i=1; i<=5; i++) { for(int j=1; j<=10; j++) { // 반복 수행할 문장 } // 반복 수행할 문장 }
3) 향상된 for문(enhanced for statement)
- JDK1.5부터 for문의 새로운 문법이 추가되었음
- 타입은 배열 또는 컬렉션의 요소의 타입이어야 함
- 배열 또는 컬렉션에 저장된 값이 매 반복마다 하나씩 순서대로 읽혀서 변수에 저장됨
for( 타입 변수명 : 배열 또는 컬렉션) { // 반복 수행할 문장 }
4) while문
- 먼저 조건식을 평가해서조건식이 거짓이면 문장 전체를 벗어나고, 참이면 블럭 내의 문장을 수행하고 다시 조건식으로 돌아감
- 조건식이 거짓이 될 때까지 이 과정이 계속 반복됨
-조건식은 생략 불가능
- 조건식에 true를 넣으면무한루프에빠질 수 있음에 주의
while (조건식) { // 조건식의 연산결과가 참(true)일 동안, 반복 수행할 문장 }
5) do-while문
-최소한 한번은 수행될 것을 보장하는 while문
- while문과 반대로블록 내의 문장을 먼저 수행한 후 조건식을 평가함
- while(조건식) 뒤에세미콜론(;)을 붙여야 함에 유의
do { // 조건식의 연산결과가 참(true)일 동안, 반복 수행할 문장 }while (조건식);
6) break문
- 자신이 포함된가장 가까운 반복문을 벗어남
- 무한 반복문에 조건문과 break문을 같이 사용할 수 있음
7) continue문
- 반복이 진행되는 도중에 continue문을 만나면반복문의 끝으로 이동하여 다음 반복으로 넘어감
- 전체 반복 중에 특정조건을 만족하는 경우를 제외하고자 할 때 사용
8) 이름 붙은 반복문
- 여러 개의 반복문이 중첩된 경우 break문으로 중첩 반복문을 완전히 벗어날 수 없음
- 중첩 반복문 앞에 이름을 붙이고 break문과 continue문에 이름을 지정해 줌으로써 하나 이상의 반복문을 벗어나거나 반복을 건너뛸 수 있음