아이템 49 - 매개변수가 유효한지 검사하라
- 함수의 맨 처음에 가급적 매개변수의 유효성을 검사하는 것이 좋다 → null 체크, index 음수 체크 등
- 중간 계산 과정에서 자연스럽게 나오는 것은 굳이 할 필요는 없다.
- public, protected 메서드는 매개변수가 잘못됐을 때 던지는 예외를 문서화해야함 → @throws
아이템 50 - 적시에 방어적 복사본을 만들라
final class Period {
private final Date start;
private final Date end;
public Period(Date start, Date end) {
if (start.compareTo(end) > 0) {
throw new IllegalArgumentException();
}
this.start = start;
this.end = end;
}
public Date start() {
return start;
}
public Date end() {
return end;
}
}
public static void main(String[] args) {
Date start = new Date();
Date end = new Date();
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()을 호출할 수 있기 때문 ← 다형성
- 이렇게 해도 아직 수정 가능
public static void main(String[] args) {
Date start = new Date();
Date end = new Date();
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 - 다중정의는 신중히 사용하라
public class CollectionClassifier {
public static String classify(Set<?> s) {
return "집합";
}
public static String classify(List<?> s) {
return "리스트";
}
public static String classify(Collection<?> s) {
return "그 외";
}
public static void main(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 중 어느 메서드를 호출할지가 컴파일 타임에 결정되기 때문
- 오버라이딩한 메소드는 동적으로 선택되고, 오버로드한 메소드는 정적으로 선택된다
public class Overriding {
public static void main(String[] args) {
List<Wine> wineList = List.of(new Wine(), new SparklingWine(), new Champagne());
for (Wine wine : wineList) {
System.out.println(wine.name());
}
}
}
class Wine {
String name() {
return "포도주";
}
}
class SparklingWine extends Wine {
@Override
String name() {
return "발포성 포도주";
}
}
class Champagne extends SparklingWine {
@Override
String name() {
return "샴페인";
}
}
- 위 코드의 실행 결과는 포도주, 발포성 포도주, 샴페인 이다.
- for문 컴파일 타입이 Wine 임과 무관하게, 담겨있는 실제 객체의 메서드들 중 가장 하위에 있는 것을 호출함 → 동적 바인딩
- 따라서 다중정의는 이러한 일반적인 기대를 가볍게 무시 → 다중 정의가 혼동을 일으키는 상황을 피해야함
- 어떻게 피함?
- 매개변수 수가 같은 다중정의는 만들지 말자.
- 가변인수를 사용하는 메서드라면 다중정의를 아예 안하는 것도
아이템 53 - 가변인수는 신중히 사용하라
- 메서드를 정의할 때 필수 매개변수는 가변인수 앞에 두고, 가변인수를 사용할 때는 성능 문제까지 고려하자.
아이템 54 - null이 아닌, 빈 컬렉션이나 배열을 반환하라
- null을 리턴하게 되면
- 클라이언트에서 이를 체크하는 코드가 반드시 들어가게 됨
- 만약 체크하지 않으면 예상하지 못한 오류가 발생할 수 있다.
- 빈 컨테이너를 리턴하도록 수정하자
- 만약 성능이 걱정된다면(거의 그럴일이 없지만) - 매번 똑같은 빈 불변 컬렉션을 반환하면 됨
- Optional.empty(), Colletions.emptyList() 가 대표적
- static final로 선언하고 이를 반환하는 방식
아이템 55 - 옵셔널 반환은 신중히 하라
- null을 던지는 방식은 당연히 안좋음
- 그러나 예상 외로 조건이 안 맞을 때마다 예외를 던지는 방식도 좋지 못하다
- 예외는 진짜 예외적인 상황에서만 사용
- 스택 추적 전체를 캡처하므로 비용이 꽤 높기 때문
- 이 경우 Optional를 리턴하는 방식이 도움이 됨
- 예외를 던지는 대신 Optional.empty()를 리턴하도록
- Optional을 사용할 때는 절대 null을 반환하지 않게 → 이는 옵셔널의 취지를 완전히 무시한것
- 그러나 항상 좋은 건 아님
- 컬렉션, 스트림, 배열, 옵셔널 같은 컨테이너 타입은 옵셔널로 감싸면 안됨
- 그냥 빈 List를 반환하는게 좋다.
- 어차피 빈 List를 리턴할 수 있으므로 굳이 옵셔널을 사용할 필요가 없음
- 컬렉션의 키, 값, 원소나 배열의 원소로 사용하는 경우는 거의 없을 것
- 박싱된 기본 타입을 담는 옵셔널은 기본 타입보다 무거울수밖에 없다
- 그럼 어떤 경우에?
- 결과가 없을 수 있으며, 클라이언트가 상황을 특별 처리해야 할 때 옵셔널 사용
아이템 56 - 공개된 API 요소에는 항상 문서화 주석을 작성하라
https://product.kyobobook.co.kr/detail/S000001033066