함수의 맨 처음에 가급적 매개변수의 유효성을 검사하는 것이 좋다 → null 체크, index 음수 체크 등
중간 계산 과정에서 자연스럽게 나오는 것은 굳이 할 필요는 없다.
public, protected 메서드는 매개변수가 잘못됐을 때 던지는 예외를 문서화해야함 → @throws
아이템 50 - 적시에 방어적 복사본을 만들라
final classPeriod{
private final Date start;
private final Date end;
publicPeriod(Date start, Date end) {
if (start.compareTo(end) > 0) {
thrownew IllegalArgumentException();
}
this.start = start;
this.end = end;
}
publicDatestart() {
return start;
}
publicDateend() {
return end;
}
}
publicstaticvoidmain(String[] args) {
Date start = newDate();
Date end = newDate();
Period p = new Period(start, end);
System.out.println(p.end());
// 불변식 깨짐
System.out.println("after : ");
end.setYear(78);
System.out.println(p.end());
}
Period 클래스는 불변을 목적으로 만들었다.
그러나 Date가 가변이기 때문에, 손쉽게 수정할 수 있었다 → 불변식이 깨짐
간단한 해결 : Date 말고 Instant, LocalDateTIme 등을 사용
꼭 Date를 사용해야 한다면, 매개변수의 유효성 검사 전에 생성자에서 방어적 복사본을 만든다.
유효성 검사 전에 복사한 이유 → 멀티스레딩 환경일 경우 그 찰나의 순간에 수정할 위험이 있으므로
public Period(Date start, Date end) {
this.start = new Date(start.getTime());
this.end = new Date(end.getTime());
if (start.compareTo(end) > 0) {
throw new IllegalArgumentException();
}
}
또한, clone()을 사용하지 않았는데, 이는 매개변수 Date가 final 클래스가 아니므로 누군가 확장한 클래스의 clone()을 호출할 수 있기 때문 ← 다형성
이렇게 해도 아직 수정 가능
publicstaticvoidmain(String[] args) {
Date start = newDate();
Date end = newDate();
Period p = new Period(start, end);
System.out.println(p.end());
// 불변식 깨짐
System.out.println("after : ");
p.end().setYear(78);
System.out.println(p.end());
}
이는 start(), end() 가 내부 가변 정보를 그대로 드러내기 때문
방어 : 접근자 또한 가변 필드의 방어적 복사본을 리턴하도록
public Date start() {
return new Date(start.getTime());
}
public Date end() {
return new Date(end.getTime());
}
이 경우 clone()을 사용해도 괜찮은데, 이는 매개변수가 아닌 자기 클래스의 필드에 존재하는 값이고, Date임이 확실하기 때문이다.
방어적 복사는 무조건 이뤄져야 하는 것이 아님 → 복사 비용이 너무 크거나 클라이언트가 신뢰 가능하다면 방어적 복사 대신 해당 요소를 수정했을 때의 책임이 클라이언트에게 있음을 문서에 명시하면 됨
아이템 51 - 메서드 시그니처를 신중히 설계하라
메서드 이름 신중히 짓기
편의 메서드를 너무 많이 만들지 말자
매개변수 목록을 짧게 유지하자 ← 특히 같은 자료형이 연속된 매개변수 리스트는 어렵다. - 4개 이하로
여러 메서드로 쪼갠다 → 쪼개진 메서드 각각이 원래 매개변수 목록의 부분집합을 받음
매개변수 여러개를 묶는 도우미 클래스를 만듦 → 정적 멤버 클래스로 둔다.
빌더 패턴을 메서드 호출에 응용 → 매개변수가 많고, 일부를 생략할 수 있을 때 유용
세터를 만들고, 각 세터를 호출해 필요 값을 설정한 후 execute()로 유효성 검사
매개변수 타입으로는 인터페이스가 훨씬 나음
boolean보다는 원소 2개짜리 열거 타입이 낫다.
이는 추가하기도 쉬움
아이템 52 - 다중정의는 신중히 사용하라
publicclassCollectionClassifier{
publicstaticStringclassify(Set<?> s) {
return"집합";
}
publicstaticStringclassify(List<?> s) {
return"리스트";
}
publicstaticStringclassify(Collection<?> s) {
return"그 외";
}
publicstaticvoidmain(String[] args) {
Collection<?>[] collections = {
new HashSet<String>(),
new ArrayList<BigInteger>(),
new HashMap<String, String>().values()
};
for (Collection<?> c : collections) {
System.out.println(classify(c));
}
}
}
위 코드는 “그 외” 를 3번 출력하게 된다.
이유 : 세 개의 classify 중 어느 메서드를 호출할지가 컴파일 타임에 결정되기 때문