네로개발일기

개발자 네로의 개발 일기, 자바를 좋아합니다 !

'dev book/Effective Java'에 해당되는 글 27건


반응형

# 변경 가능성을 최소화하라.

## 불변 클래스(Immutable Class) ?

- 해당 객체의 내부적인 값들을 변경할 수 없는 클래스를 의미한다. 불변 클래스는 해당 객체가 파괴되는 순간까지 절대 달라지지 않는다.

- 불변 클래스는 가변 클래스보다 설계하고 구현하기 쉽고, side-effect로부터 안전

- 예) String, Wrapper Class, BigInteger, BigDecimal - 설계 구현 사용이 쉽다.

- 오류의 여지가 적고 안전하다.

 

## 1. 불변 클래스의 5가지 규칙

1) 객체의 상태를 변경하는 메서드(변경자, setter)를 제공하지 않는다.

2) 클래스를 확장할 수 없게 한다. - extend (X)

3) 모든 필드를 final로 선언한다.

4) 모든 필드를 private로 선언한다.

5) 자신 외에는 내부의 가변 컴포넌트에 접근할 수 없도록 한다.

 

## 2. 불변 객체의 장점

0) 단순

- 불변 객체는 생성된 시점부터 파괴될 때까지 그대로 유지한다.

 

1) 근본적으로 스레드에 안전

- 여러 스레드가 동시에 사용해도 불변 객체이기 때문에 다른 스레드에 영향을 주지 않아 안전하게 공유하고 사용할 수 있다.

- 동기화할 필요가 없다.

 

2) 불변 객체는 안심하고 공유가 가능

- 스레드 간 영향을 주고 받을 수 없기 때문

- 방어적 복사도 필요없다> clone 메서드 복사 생성자 필요 없다.

- String 클래스의 복사 생성자는 되도록 사용하지 말자

 

3) 한번 만든 인스턴스는 최대한 재활용이 가능

- 자주 사용하는 인스턴스를 생성하여, 정적 팩터리를 제공할 수 있다.

 

4) 불변 객체는 자유롭게 공유할 수 있음은 물론, 불변 객체끼리는 내부 데이터를 공유할 수 있음

 

5) 객체를 만들 때 다른 불변 객체들을 구성요소로 사용하면 이점이 많다.

- 구조가 복잡해도 불변식 유지가 수월

 

6) 불변 객체는 그 자체로 실패 원자성을 제공한다.

- 실패 원자성: 메서드에서 예외가 발생한 후에도 그 객체는 메서드 호출 전 상태와 같은 유효한 상태를 가진다.

 

## 3. 불변 객체의 단점

- 값이 다르면 반드시 독립된 객체로 만들어야 한다. 값의 가짓수가 많으면 이를 모두 만드는데 큰 비용이 필요하다.

 

1) 흔히 쓰일 다단계 연산들이 예측이 될 때

- 다단계 연산 속도를 높여주는 가변 동반 클래스를 package-privated로 둔다.

- BigInteger는 연산 속도를 높여주는 동반 클래스로 package-privated로 되어있는 MutableBigInteger/SignedMutableBigInteger/BigSieve를 사용할 것으로 예상

 

2) 예측이 되지 않을 때

- 다단계 연산 속도를 높여주는 가변 동반 클래스를 public으로 둔다.

- 예) StringBuilder/StringBuffer: String 클래스의 연산 속도를 높이기 위함

 

## 4. 불변 클래스를 만드는 설계 방법

1. final 클래스로 상속을 막는다.

2. 정적 팩터리를 제공하는 방법

- 모든 생성자를 private 혹은 package-private로 만들고 public 정적 팩터리를 제공한다. (캐싱 기능 추가도 가능하다.) public이나 protected 생성자가 없으니 다른 패키지에서는 이 클래스를 확장하는 게 불가능하기 때문이다.

 

## 정리

- 모든 클래스를 불변으로 만들 수는 없기 때문에 클래스라도 변경 가능한 부분을 최소한으로 줄이는 것이 좋다.

- 클래스가 꼭 필요한 경우가 아니면 불변이어야 한다.

- 다른 합당한 이유가 없다면 모든 필드는 private final이어야 한다.

- 생성자는 불변식 설정이 모두 완료된 초기화가 완벽히 끝난 상태의 객체를 생성해야 한다.

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

# public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라.

## 1. public 클래스의 가변 필드

- 절대 가변 필드를 public으로 노출하면 안된다.

* 캡슐화의 이점을 제공하지 못한다.
* API를 수정하지 않고는 내부 표현을 바꿀 수 없다.
* 불변식을 보장할 수 없다.
* 외부에서 필드에 접근할 때 부수 작업을 수행할 수 없다.

- 패키지 바깥에서 접근할 수 있는 클래스라면 접근자(getter)를 제공하자.
- 클래스 내부의 표현 방식을 언제든 바꿀 수 있다.

- package-private 클래스, private 중첩 클래스는 데이터 필드를 노출해도 괜찮다.
* package-private: 패키지 바깥 코드를 손대지 않고 데이터 표현 방식을 바꿀 수 있다.
* private 중첩: 이 클래스를 포함하는 외부 클래스까지로 제한한다.

## 2. public 클래스의 불변 필드

- 직접 노출할 때의 단점이 줄어들긴 하지만 좋은 방법은 아니다.
- 단점1. API를 변경하지 않고는 표현방식을 바꿀 수 없다.
- 단점2. 필드를 읽을 때 부수적인 작업을 수행할 수 없다.
- 장점1. 불변식을 보장할 수 있다.



728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

# 클래스와 멤버의 접근 권한을 최소화하라. => 정보 은닉/캡슐화

- 잘 설계된 컴포넌트: 클래스 내부 데이터와 내부 구현 정보를 외부 컴포넌트로부터 얼마나 잘 숨겼는가/ 오직 API를 통해서만 다른 컴포넌트와 소통 [정보 은닉/캡슐화]

 

## 1. 정보 은닉의 장점: 개발 속도, 디버깅, 성능의 최적화, 재사용성, 동작 검증

1. 시스템 개발 속도를 높인다. 여러 컴포넌트를 병렬로 개발할 수 있기 때문

2. 각 컴포넌트를 빠르게 파악하여 디버깅 기능, 다른 컴포넌트로 교체하는데 부담도 적다.

3. 정보 은닉이 성능을 높여주지는 않지만, 성능의 최적화에는 도움이 된다.

4. 재사용성을 높여준다. 외부에 의존하지 않고 독자적으로 동작할 수 있는 컴포넌트라면, 그 컴포넌트와 함께 개발되지 않은 환경에서도 유용하게 쓰일 가능성이 크다.

5. 시스템 전체가 개발되지 않은 상태에서도 개별 컴포넌트의 동작을 검증할 수 있다.

 

## 2. 정보 은닉의 기본 원칙

1) 모든 클래스와 멤버의 접근성을 가능한 좁혀라.

- 클래스와 멤버의 접근 제한자를 private 등으로 낮은 접근 수준을 부여하는 것이다.

- 우리가 가장 많이 사용하는 클래스에는 public과 package-private 두 가지를 가장 많이 사용한다. Top Level Class 나 Interface를 public으로 선언하면 공개 API가 되지만 package-private로 선언하면 해당 패키지안에서만 사용 가능하다.

- 패키지 외부에서 클래스를 사용할 일이 없다면 package-private로 선언하여 API가 아닌 내부에서 언제든지 수정할 수 있게 한다.

 

2) 멤버에 부여할 수 있는 접근 제한자

- 멤버: 필드, 메서드, 중첩 클래스, 중첩 인터페이스

- private/ package-private/ protected/ public

1. private: 해당 멤버를 선언한 클래스에서만 접근이 가능.

2. package-private(default): 해당 멤버가 소속된 패키지 안의 모든 클래스에서 접근할 수 있다. 접근 제한자를 명시하지 않았을 때 적용되는 default 접근 제한자. (단, interface는 default public이다.)

3. protected: package-private의 범위를 포함하고, 해당 멤버를 선언한 클래스를 상속받은 하위 클래스에서도 접근이 가능한 접근 제한자.

4. public: 모든 곳에서 접근이 가능한 접근 제한자.

 

* 주의: public 클래스의 protected 멤버는 공개 API

 

## 3. 멤버 접근성의 제약

상위 클래스의 메서드를 재정의할 때 상위보다 좁게 설정할 수 없다.

 

## 4. 주의점

- 코드 테스트만을 위해 클래스, 인터페이스, 멤버를 공개 API로 만들면 안된다.

1) public 클래스의 인스턴스 필드는 되도록 public이 아니어야 한다.

- 필드가 가변 객체를 참조하거나, final이 아닌 인스턴스 필드를 public으로 선언하면 그 필드에 값을 제한할 수 없기 때문에 불변성을 보장할 수 없게 된다. 또한 필드가 수정될 때 다른 작업을 할 수 없게 되기 때문에, public 가변 필드를 갖는 클래스는 일반적으로 스레드로부터 안전하지 않다.

- 또한, 필드가 final이면서 불변 객체를 참조하더라도 문제는 발생할 수 있다. 이유는 final이기 때문에 다른 객체를 참조하도록 변경할 수 없지만, 이미 참조하고 있는 참조된 객체 자체는 수정될 수도 있다.

 

2) 정적 필드에서의 public 필드 예외

- public static final 필드 공개는 허용한다. 이런 필드는 반드시 기본 타입 값이나 불변 객체를 참조해야 한다.

 

3) 클래스에서 public static final 배열 필드를 두거나 이 필드를 반환하는 접근자 메서드를 제공해서는 안된다.

- 클라이언트에서 배열의 배용을 수정할 수 있기 때문이다.

[권장 방법]

1. public 불변 리스트를 추가

private static final Thing[] PRIVATE_VALUES = {...};
public static final List<Thing> VALUES = Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));

 

2. 복사본을 반환하는 public 메서드를 추가: 방어적 복사

private static final Thing[] PRIVATE_VALUES = {...};
public static final Thing[] values() {
	return PRIVATE_VALUES.clone();
}

 

- 타입과 성능에 따라서 사용

 

## 5. Java9: 모듈 시스템 개념의 도입

- 패키지: 클래스의 묶음

- 모듈: 패키지의 묶음, 자신이 속하는 패키지 중 공개(export)할 것을 module-info.java 파일에 선언한다. 클래스를 외부에 공개하지 않으면서도 같은 모듈을 이루는 패키지 사이에서 자유롭게 공유할 수 있다.

- 암묵적 접근 수준: public, protected 수준의 효과가 모듈 내부로 한정된다.

 

1) 모듈에 적용되는 새로운 두 접근 수준은 주의해야 한다.

- 모듈의 *.jar 파일을 모듈 경로가 아니라 애플리케이션 클래스 패스에 두면, 모듈 안 패키지가 모듈이 없는 것처럼 행동

- JDK: 자바 라이브러리에서 공개하지 않은 패키지는 모듈 밖에서 접근할 수 없다.

 

2) 모듈의 장점을 누리기 위한 조치

- 패키지를 모듈 단위로 묶는다.

- 모듈 선언에 패키지들의 의존성을 명시한다.

- 소스 트리 재배치

- 모듈 안으로부터(모듈 시스템을 적용하지 않는) 일반 패키지로의 모든 접근에 특별한 조치를 취해야 한다.

 

 

summary

1. 접근성은 가능한 최소한으로

2. 꼭 필요한 정보들만 public API로 설계

3. class, interface, field가 의도치 않게 public으로 공개되지 않도록

4. public 클래스는 상수용 public static final 필드 외에는 public 필드 없이

5. public static final이 참조하는 객체가 가변인지 불변 객체인지 확인

 

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

# Comparable을 구현할지 고려하라.

- Comparable을 구현하는 이유는 클래스의 인스턴스들 간의 ordering을 목적으로 구현하는 클래스이다. 따라서 Comparable을 구현한 클래스에 대한 배열은 다음처럼 손쉽게 정렬할 수 있다.

Arrays.sort(arr);

 

 

- 사실상 자바 플랫폼 라이브러리의 모든 값 클래스와 열거타입은 Comparable을 구현했다. 알파벳, 숫자, 연대와 같이 순서가 명확한 값 클래스를 작성한다면, 반드시 Comparable 인터페이스를 구현하자 !

 

## 1. compareTo 메서드 규약

- 이 객체와 주어진 객체의 순서를 비교한다.

- 이 객체가 주어진 객체보다 작으면 음의 정수를, 같으면 0을, 크면 양의 정수를 리턴한다.

- 이 객체와 비교할 수 없는 타입이 주어지면 ClassCaseException을 던진다.

 

1) 대칭성

- Comparable을 구현한 클래스는 모든 x, y에 대해 x.compareTo(y) == y.compareTo(x) * -1을 만족해야 한다.

- 따라서, x.compareTo(y)가 예외를 던지는 경우, y.compareTo(x)도 예외를 던져야 한다.

 

2) 추이성

- Comparable을 구현한 클래스는 모든 x, y, z에 대해 x.compareTo(y) > 0 이고 y.compareTo(z)이면, x.compareTo(z) > 0을 만족해야 한다.

 

3) 반사성

- Comparable을 구현한 클래스는 모든 x, y, z에 대해 x.compareTo(y) == 0 이면, sgn(x.compareTo(z)) == sgn(y.compareTo(z))를 만족해야 한다.

 

4) 동치 equals

- Comparable을 구현한 클래스는 모든 x, y에 대해 x.compareTo(y) == 0이면, x.equals(y)를 만족하는 것이 좋다. (권고사항은 아니다.) 이 권고를 지키지 않으려면 ```주의: 이 클래스의 순서는 equals 메서드와 일관되지 않다.```라고 명시해주자.

 

## 2. compareTo 안티패턴

- Comparable은 타입을 인수로 받는 제네릭 인터페이스이다. 메서드의 인수타입은 컴파일 시점에 정해진다.

- compareTo 메서드는 필드의 동치가 아니라 순서를 비교한다. Comparable을 구현하지 않았다면 Comparator를 사용할 수 있다.

- compareTo 메서드에서 관계연산자 (< 와 >)를 사용하지 말아야 한다.

- 대신 Type.compare(T t1, T t2)를 사용하여 비교하는 것이 좋다.

* 안티 패턴

public int compareTo(int x, int y) {
	return x < y ? (x == y) ? 0 : -1;
}

 

* 아래 방법을 사용하자.

Integer.compare(a, b);
Float.compare(a, b);
Double.compare(a, b);

 

- hashCode의 차를 이용한 비교는 안된다. (추이성에 위배된다.)

static Comparator<Object> hashCodeOrder = new Comparator<>() {
	(Object o1, Object o2) -> o1.hashCode() - o2.hashCode();
}

 

- 위의 코드를 실행하면 정수 overflow를 일으키거나 IEEE754 부동소수점 계산방식에 따른 오류를 발생시킬 수 있다.

- 아래와 같이 사용하자.

static Comparator<Object> hashCodeOrder = new Comparator<>() {
	(Object o1, Object o2) -> Integer.compare(o1.hashCode(), o2.hashCode());
}
static Comparator<Object> hashCodeOrder = Comparator.comparingInt(o -> o.hashCode());

 

 

## 3. 사용 예

1) 기본 타입 필드가 여러개일 때 비교자

public int compareTo(PhoneNumber pn) {
	int result = Short.compare(this.areaCode, pn.areaCode);
	if (result == 0) {
		result = Short.compare(this.prefix, pn.prefix);
		if (result == 0) {
			result = Short.compare(this.lineNum, pn.lineNum);
		}
	}
	return result;
}

 

- 필드의 정렬 우선순위를 정해 같은 값이 있을 때마다 조건을 추가한다.

 

2) 비교자 생성 메서드를 이용한 비교자

private static final Comparator<PhoneNumber> COMPARATOR
	= comparingInt((PhoneNumber pn) -> pn.areaCode)
		.thenComparingInt(pn -> pn.prefix)
		.thenComparingInt(pn -> pn.lineNum);

 

- comparingInt라는 static 메서드를 import하여 사용하고, 두번째 조건부터 thenComparingInt를 사용하여 비교자를 추가할 수 있다.

- 최초 사용시, PhoneNumber pn을 사용하여 람다식에서 타입을 추론할 수 있도록 코드를 추가하였다.

- thenComparingInt 부터는 자바 컴파일러가 충분히 타입을 추론할 수 있으므로 명시적으로 지정하지 않았다.

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !

반응형

# clone 재정의는 주의해서 진행하라.

- Cloneable은 복제해도 되는 인터페이스임을 명시하는 용도의 mixin 인터페이스이다. 하지만 아쉽게도 의도한 목적을 이루지 못했다. 가장 큰 문제는 clone 메서드가 선언된 곳이 Cloneable이 아닌 Object이고, 그마저도 protected로 되어있다. 즉, Cloneable을 구현하는 것만으로는 외부에서 clone 메서드를 호출할 수 없다.

 

## 1. Cloneable 인터페이스

/**
* A class implements the <code>Cloneable</code> interface to
* indicate to the {@link java.lang.Object#clone()} method that it
* is legal for that method to make a
* field-for-field copy of instances of that class.
* <p>
* Invoking Object's clone method on an instance that does not implement the
* <code>Cloneable</code> interface results in the exception
* <code>CloneNotSupportedException</code> being thrown.
* <p>
* By convention, classes that implement this interface should override
* {@code Object.clone} (which is protected) with a public method.
* See {@link java.lang.Object#clone()} for details on overriding this
* method.
* <p>
* Note that this interface does <i>not</i> contain the {@code 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.
*
* @author unascribed
* @see java.lang.CloneNotSupportedException
* @see java.lang.Object#clone()
* @since 1.0
*/
public interface Cloneable {
}

 

- 자바의 Cloneable 인터페이스를 보면 아무런 메서드도 없다.

- 아무것도 없지만, 사실은 Object의 clone 메서드의 동작방식을 결정한다.

- Cloneable을 구현한 클래스의 인스턴스에서 clone을 호출하면, 그 객체의 필드들을 하나하나 복사한 객체를 반환하며, 그렇지 않은 클래스의 인스턴스에서 호출하면, ClassNotSupportedException을 던진다.

 

## 2. Object 클래스의 clone 규약

@HotSpotIntrinsicCandidate
protected native Object clone() throws CloneNotSupportedException;

 

- Object에 명시된 clone 규약이 주석으로 쓰여져 있다.

- x.clone() != x 는 참이다. 복사한 객체와 원본 객체는 서로 다른 객체이다.

- x.clone().getClass() == x.getClass()는 일반적으로 참이다. 하지만 반드시 만족해야하는 것은 아니다. 관례상, 이 방법으로 반환된 객체는 독립성이 있어야 한다. 이를 만족하려면 super.clone()으로 얻은 객체의 필드 중 하나 이상을 반환 전에 수정할 수도 있다.

- x.clone().equals(x)는 참이다. 복사한 객체와 원본 객체는 논리적 동치성이 같다.

- Cloneable을 구현하지 않은 경우, CloneNotSupportedException이 throw된다.

- 모든 Array는 Cloneable을 구현하도록 고려되었다. clone() 메서드는 T[] (T는 기본타입 또는 참조타입)를 리턴하도록 설계되었다.

- 기본적으로 Object.clone()은 clone 대상 클래스에 대해 새로운 객체를 new로 생성

- 모든 필드들에 대해 초기화를 진행한다.

- 하지만 필드에 대한 객체를 다시 생성하지 않는 Shallow copy 방식으로 수행한다. (deep copy 아님.)

- 클래스에 대한 복제본을 원하지 않는 경우 clone 메서드를 재정의하여 CloneNotSupportedException을 throw하도록 한다.

 

## 3. clone 메서드 재정의시, 주의할 점 !

### 1. 기본적인 clone 메서드 재정의

class PhoneNumber implements Cloneable {

	@Override
	public PhoneNumber clone() {
		try {
			return (PhoneNumber) super.clone();
		} catch(ClassNotSupportedException e) {
		// 아무처리를 하지 않거나, RuntimeException 로 감싸는 것이 사용하기 편하다.
		}
	}
}

 

- super.clone()을 실행하면 PhoneNumber에 대한 완벽한 복제가 이루어진다.

- super.clone()의 리턴타입은 Object이지만, 자바의 공변 변환 타이핑 기능을 이용해 PhoneNumber 타입으로 캐스팅하여 리턴하는 것이 가능하다.

- try-catch 부분으로 감싼 것은 super.clone() 메서드에서 ClassNotSupportedException이라는 checked exception을 리턴하기 때문에 처리해주었다. 하지만 PhoneNumbe가 Cloneable을 구현하기 때문에 절대 실패하지 않는다. 따라서 이부분은 RuntimeException으로 처리하거나, 아무것도 설정하지 않아야 한다.

 

### 2. 가변 상태를 갖는 필드에 대한 복제

public class Stack implements Cloneable{
	private Object[] elements;
	private int size = 0;
	private static final int DEFAULT_INITIAL_CAPACITY = 16;

	public Stack() {
		this.elements = new Object[DEFAULT_INITIAL_CAPACITY];
	}

	public void push(Object o) {}
}
...

	@Override
	public Stack clone() {
		try {
			Stack result = (Stack) super.clone();
			result.elements =
		} catch(CloneNotSupportedException e) {
		}
	}
}

 

- 단순히 clone() 메서드를 이용해 super.clone()만 실행하게 된다면, new Stack()을 통해 새로운 객체가 생성되고 필드 모두 원본 객체와 동일하게 초기화가 될 것이다. 하지만, Object의 clone 기본 규약에는 deep copy가 아닌 shallow copy를 이요해 초기화를 진행한다고 적혀있다. 따라서, 배열과 같은 가변필드는 원본 필드와 객체를 공유하게 된다.

- clone() 메서드는 사실상 생성자와 같은 효과를 낸다. 즉, clone은 원본 객체에 아무런 해를 끼치지 않는 동시에 복제된 객체의 불변식을 보장해야 한다. 그렇기 때문에 제대로 복제하기 위해서는 elements라는 배열을 똑같이 복사해서 만들어줘야 한다.

 

### 3. 배열 복사

- 배열을 복제하는 방법 중 가장 권장하는 방법은 array.clone()을 이용해 복사하는 방법이다. 사실, 배열은 clone기능을 가장 제대로 사용하는 유일한 예이다.

하지만, array 필드가 final이 적용되어 있다면 array.clone()을 통해 초기화를 할 수 없다. (final이기 때문에 객체 생성이후 초기화 불가)

- Cloneable 아키텍처는 가변 객체를 참조하는 필드는 final로 선언하라 라는 일반 용법과 충돌한다. 그래서 복제할 수 있는 클래스를 만들기 위해 일부 필드에서 final 한정자를 제거해야할 수도 있다.

 

### 4. stack-overflow 문제

public class HashTable implements Cloneable {
	private Entry[] buckets = ...;
	private static class Entry {
		final Object key;
		Object value;
		Entry next;

		Entry(Object key, Object value, Entry next) {
			this.key = key;
			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) {
			throw new Assertion();
		}
	}
}

- 복제본은 자신만의 버킷 배열은 갖지만, 배열 내의 Entry는 원본과 같은 연결리스트를 참조하여, 불변성이 깨지게된다.

- 그래서 HashTable.Entry 클래스는 내부적으로 deep copy를 지원하도록 보강되었다. 연결리스트에 대한 next를 복제할 때 재귀적으로 clone을 호출하도록 되어있는데, 재귀호출 때문에 연결리스트의 size만큼 stack frame을 소비하여, 리스트가 길면 stack-overflow 에러를 발생시킬 위험이 있다.

- 이 문제를 해결하기 위해 재귀 호출을 통한 deep copy 대신 반복자를 써서 순회하는 방법으로 수정해야 한다.

 

### 5. 생성자 내에서는 재정의될 수 있는 메서드를 호출하지 말자.

- 만약 clone이 하위 클래스에서 재정의한 메서드를 호출하면, 하위 클래스는 복사과정에서 자신의 상태를 교정할 기회를 잃게 되어 원본과 복제본의 상태가 달라질 수 있다.

 

### 6. 스레드 안전성을 고려한다면 적절히 동기화해야 한다.

- 스레드 안정성을 고려한가면 clone() 메서드에 대해 적절히 동기화 처리를 해야한다.

@Override
public synchronized Object clone() {
	try {
		Object result = super.clone();
	} catch(CloneNotSupportedException e) {
	}
}

 

## 4. clone() 재정의 방법

1. Cloneable을 구현하는 모든 클래스는 clone()을 재정의해야 한다.

2. 접근 제한자는 public으로

3. 반환타입은 클래스 자신으로

4. 가장 먼저 super.clone()을 호출한 후 필요한 필드를 적절히 수정한다.

5. 이후 깊은 구조까지 클론한다. (보통 재귀지만, 항상 정답은 아니다.)

6. 기본 타입 필드, 불변 객체 참조만 갖는 클래스면 아무 필드도 수정할 필요가 없다.

7. 고유 ID는 비록 기본 타입, 불변이어도 수정해야 한다.

 

## 5. clone()재정의 대체법

* 복사 생성자: 단순히 자신과 같은 클래스의 인스턴스를 인수로 받는 생성자

public Yum(Yum yum) {...}

 

* 복사 팩터리

public static Yum newInstance(Yum yum) {...}

* 복사 생성자와 복사 팩터리의 장점

1. 위험한 객체 생성 매커니즘을 사용하지 않는다. (생성자없이 객체 생성)

2. 문서화된 규약에 기대지 않는다.

3. final 필드 용법과 충돌하지 않는다.

4. 불필요한 검사 예외를 던지지 않는다.

5. 형변환 필요가 없다.

6. 해당 클래스가 구현한 '인터페이스' 타입의 인스턴스를 인수로 받을 수 있다.

- 인터페이스 기반 복사 생성자 = 변환 생성자 (conversion constructor)

- 인터페이스 기반 복사 팩터리 = 변환 팩터리 (conversion factory)

728x90
반응형
blog image

Written by ner.o

개발자 네로의 개발 일기, 자바를 좋아합니다 !