📌 8장 : 메서드
아이템 49. 매개변수가 유효한지 검사하라
- 매개변수 검사를 제대로 하지 않았을 경우
- 메서드가 수행되는 중간에 모호한 예외를 던지며 실패할 수 있다.
- 메서드가 잘 수행되지만 잘못된 결과를 반환할 수 있다.
- 메서드는 수행되지만 어떤 객체를 이상한 상태로 만들어 추후에 메서드와는 관련 없는 오류가 발생할 때
- 위와 같이 매개변수 검사에 실패하면 실패 원자성(failure atomicity)을 어기게 된다.
- public과 protected 메서드는 매개변수 값이 잘못되는 경우 던지는 예외들을 문서화 해야 한다.
- @throws 자바독 태그 사용
- 예외 종류 : IllegalArgumentException, IndexOutOfBoundsException, NullPointerException
- 제약을 어겼을 경우의 예외도 추가해야 한다.
public Biginteger mod(Biginteger m) {
if (m.signum() <= 0)
throw new ArithmeticException("계수(m)는 양수여야 합니다. " + m);
... // 계산 수행
}
- private 메서드에서 assert를 사용해 매개변수 유효성 검증
private static void sort(long a[], int offset, int length){
assert a != null;
assert offset >= 0 && offset <= a.length;
assert length >= 0 && length <= a.length - offset;
... // 계산 수행
}
- assert(단언문) 특징
- 실패하면 AssertionError를 던진다.
- 런타임에 아무런 효과도, 아무런 성능 저하도 없다.
아이템 50. 적시에 방어적 복사본을 만들라
- 자바는 네이티브 메서드를 사용하지 않아 C, C++ 같은 언어에서 흔히 볼 수 있는 버퍼 오버런, 배열 오버런, 와일드 포인터 같은 메모리 충돌 오류에서 안전하다.
- 자바가 다른 클래스의 침범을 모두 막을 수 있는 것은 아니기 때문에, 방어적으로 프로그래밍해야 한다.
- 어떤 객체든 그 객체의 허락 없이 외부에서 내부를 수정하는 일은 불가능하다.
- 예외 상황 : 불변식을 지키지 못한 경우
public 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(
start + "가 " + end + "보다 늦다.");
this.start = start;
this.end = end;
}
public Date start() {
return start;
}
public Date end() {
return end;
}
...// 나머지 코드 생략
}
/* Period 인스턴스 내부 공격 */
Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
end.setYear(78); // p의 내부를 수정할 수 있다.
- Date는 오래된 API이기 때문에 새로운 코드 작성에서는 사용을 하지 않도록 한다.
- Date 대신 불변인 Instant를 사용하면 된다.
- 또는 LocalDateTime, ZonedDateTime 사용
- 외부 공격으로부터 인스턴스 내부를 보호하기 위해서는 생성자에서 받은 가변 매개변수 각각을 방어적 복사(defensive copy)해서 인스턴스 내부에서 원본이 아닌 복사본을 사용한다.
public Period(Date start, Date end) {
this.start = new Date(start.getTime());
this.end = new Date(end.getTime());
if (this.start.compareTo(this.end) > 0)
throw new IllegalArgumentException(
this.start + "가 " + this.end + "보다 늦다.");
}
- 접근자 메서드가 내부의 가변 정보를 직접 드러내는 문제점
public Date start() {
return new Date(start.getTime());
}
public Date end() {
return new Date(end.getTime());
}
아이템 51. 메서드 시그니처를 신중히 설계하라
- API 설계 요령 리스트
- 메서드 이름을 신중하게 짓자.
- 편의 메서드를 너무 많이 만들지 말자.
- 매개변수 목록은 짧게 유지하자. (4개 이하)
- 매개변수 목록을 짧게 줄이는 방법
- 여러 메서드로 쪼갠다.
- 매개변수 여러 개를 묶어주는 도우미 클래스를 만든다.(정적 멤버 클래스)
- 객체 생성에 사용한 빌더 패턴을 메서드 호출에 응용한다. 먼저 모든 매개변수를 하나로 추상화한 객체를 정의하고, 클라이언트에서 이 객체의 setter를 호출해 필요한 값을 설정한다.
- 매개변수 타입으로는 클래스보다 인터페이스가 더 낫다.
- 클래스를 사용하면 클라이언트에게 특정 구현체만 사용하도록 제한하게 된다.
- boolean보다 원소 2개짜리 열거 타입이 더 낫다.
- public enum TemperatureScale { FAHRENHEIT, CELSIUS }
아이템 52. 다중정의는 신중히 사용하라
public class Collectionclassifier {
public static String classify(Set<?> s) {
return "집합";
}
public static String classify(List<?> 1st) {
return "리스트";
}
public static String classify(Collection<?> c) {
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번 출력한다.
- 다중정의(overloading)된 classify는 어느 메서드를 호출할지가 컴파일 타임에 정해지기 때문이다.
- 컴파일 타임에 for문에 있는 c는 항상 Collection<?> 타입
- 컴파일타임의 매개변수를 기준으로 classify(Collection<?>) 만 호출한다.
- 재정의 메서드 호출 메커니즘
class Wine {
String name() { return "포도주"; }
}
class SparklingWine extends Wine {
@Override String name() { return "발포성 포도주"; }
}
class Champagne extends SparklingWine { (
@Override String name() { return "샴페인"; }
}
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. printIn(wine.name());
}
}
- 출력 결과 : "포도주", "발포성 포도주", "샴페인"
- for문에 있는 타입과 무관하게 가장 하위에서 정의한 재정의 메서드가 실행된다.
- 다중정의 메서드에서는 객체의 런타임 타입이 중요하지 않다.
아이템 53. 가변인수는 신중히 사용하라
- 가변인수(varargs) 메서드는 명시한 타입의 인수를 0개 이상 받을 수 있다.
- 메서드를 호출하면, 먼저 인수의 개수와 길이가 같은 배열을 만들고 인수들을 배열에 저장하여 가변인수 메서드에 전해준다.
static int sum(int... args) {
int sum = 0;
for (int arg : args)
sum += arg;
return sum;
}
- 인수가 1개 이상이어야 하는 가변인수 메서드 : 컴파일 타임이 아닌 런타임에 실패한다는 단점이 있다.
static int min(int... args) {
if (args.length = 0)
throw new IllegalArgumentException("인수가 !개 이상 필요합니다.");
int min = args[0];
for (int i = 1; i < args.length; i++)
if (args[i] < min)
min = args[i];
return min;
}
- 매개변수를 2개 받는 방법을 사용하면 문제를 해결할 수 있다.
static int min(int firstArg, int... remainingArgs) {
int min = firstArg;
for (int arg : remainingArgs)
if (arg < min)
min = arg;
return min;
}
아이템 54. null이 아닌, 빈 컬렉션이나 배열을 반환하라
- null 을 반환하게 되면 API 사용이 어려워지고 오류 처리 코드가 늘어난다.
- 빈 컬렉션 반환 코드 예시
public List<Cheese> getCheeses() {
return new ArrayList<>(cheesesInStock);
}
// 최적화 : 빈 컬렉션을 매번 할당하지 않는 코드
public List<Cheese> getCheeses() {
return cheeseInStock.isEmpty() ? Collections.emptyList()
: new ArrayList<>(cheesesInStock);
}
아이템 55. 옵셔널 반환은 신중히 하라
- 자바 8 이전에 값을 반환할 수 없는 경우
- 예외 생성할 경우의 스택 추적 전체를 캡처하는 비용 발생
- null 값을 갖고있으면 추후에 NullPointerException 발생 위험이 있다.
- 자바 8 에서 Optional으로 null이 아닌 T 타입 참조를 하나 담거나 아무것도 담지 않을 수 있다.
- Optional(옵셔널)은 원소를 최대 1개 가질 수 있는 불변 컬렉션
- 컬렉션에서 최댓값을 구하는 메소드
public static <E extends Comparable<E>> E max(Collection<E> c) {
if (c.isEmpty())
throw new IllegalArgumentException("빈 컬렉션");
E result = null;
for (E e : c)
if (result = null || e.compareTo(resuLt) > 0)
result = Objects.requireNonNull(e);
return result;
}
public static <E extends Comparable<E>>
Optional<E> max(Collection<E> c) {
if (c.isEmpty())
return Optional.empty();
E result = null;
for (E e : c)
if (result = null || e.compareTo(resuLt) > 0)
result = Objects.requireNonNull(e);
return result;
}
- 옵셔널을 반환하는 메서드에서는 절대 null 반환하지 않기
아이템 56. 공개된 API 요소에는 항상 문서화 주석을 작성하라
- API를 올바르게 문서화하기 위해서는 공개된 모든 클래스, 인터페이스, 메서드, 필드 선언에 문서화 주석을 달아야 한다.
- 메서드용 문서화 주석에는 해당 메서드와 클라이언트 사이의 규약을 명료하게 기술해야 한다.
- 표준 규약을 지켜서 작성한다.