← 학습기록 (Java · 이펙티브 자바 3E)

8장 - 메서드

49. 매개변수가 유효한지 검사하라

메서드 매개변수의 값의 제약을 둔다면 반드시 문서화해야 하며 메서드 몸체가 시작되기 전에 검사해야 한다.

오류를 발생한 즉시 잡지 못하면 해당 오류를 감지하기 어려워지고, 감지하더라도 오류의 발생 지점을 찾기 어려워진다.

메서드 몸체가 실행되기 전에 매개변수를 확인한다면 잘못된 값이 넘어왓을 때 즉각적이고 깔끔한 방식으로 예외를 던질 수 있다.

매개변수 검사를 제대로 하지 못하면 몇 가지 문제가 생길 수 있다.

첫 번째, 메서드가 수행되는 중간에 모호한 예외를 던지며 실패할 수 있다.

더 나쁜 상황은 메서드가 잘 수행되지만 잘못된 결과를 반환할 때다.

한층 더 나쁜 상황은 메서드는 문제없이 수행됐지만, 어떤 객체를 이상한 상태로 만들어놓아서 미래의 알 수 없는 시점에 이 메서드와는 관련 없는 오류를 낼 때다.

다시 말해 매개변수 검사에 실패하면 실패 원자성을 어기는 결과를 낳을 수 있다.

public과 protected 메서드는 매개변수 값이 잘못됐을 때 던지는 예외를 문서화해야 한다.

보통은 IllegalArgumentException, IndexOutOfBoundsException, NullPointerException중 하나가 될 것이다.

클래스 수준 주석은 그 클래스의 모든 public 메서드에 적용되므로 각 메서드에 일일이 기술하는 것보다 훨씬 깔끔한 방법이다. (@Nullable)

자바 7에 추가된 java.util.Obejcts.requireNonNull 메서드는 유연하고 사용하기도 편하니, 더 이상 null 검사를 수동으로 하지 않아도 된다.

반환값은 그냥 무시하고 필요한 곳 어디서든 순수한 null 검사 목적으로 사용해도 된다.

public이 아닌 메서드라면 단언문(assert)를 사용해 매개변수 유효성을 검증할 수 있다.

assert a != null;

단언문들은 자신이 단언한 조건이 무조건 참이라고 선언한다는 것이다.

단언문은 몇 가지 면에서 일반적인 유효성 검사와 다르다.

첫 번째, 실패하면 AssertionError를 던진다.

두 번째, 런타임에서 아무런 효과도, 아무런 성능 저하도 없다.

50. 적시에 방어적 복사본을 만들라

자바는 안전한 언어다. 이것이 자바를 쓰는 즐거움 중 하나다. 네이티브 메서드를 사용하지 않으니, C, C++과 같이 안전하지 않은 언어에서 흔히 보는 버퍼 오버런, 배열 오버런, 와일드 포인터 같은 메모리 충돌 오류에서 안전하다.

자바로 작성한 클래스는 시스템의 다른 부분에서 무슨 짓을 하든 그 불변식이 지켜진다.

메모리 전체를 하나의 거대한 배열로 다루는 언어에서는 누릴 수 없는 강점이다.

하지만 아무리 자바라 해도 다른 클래스로부터 침범을 아무런 노력 없이 다 막을 수 있는 건 아니다.

그러니 클라이언트가 여러분의 불변식을 깨뜨리려 혈안이 되어 있다고 가정하고 방어적으로 프로그래밍해야 한다.

실제로도 악의적인 의도를 가진 사람들이 시스템의 보안을 뚫으려는 시도가 늘고 있다.

평범한 프로그래머도 순전히 실수로 여러분의 클래스를 오동작하게 만들 수 있다.

물론 후자의 상황이 더 흔하다.

어떤 경우든 적절치 않은 클라이언트로부터 클래스를 보호하는 데 충분한 시간을 투자하는 게 좋다.

어떤 객체든 그 객체의 허락 없이는 외부에서 내부를 수정하는 일은 불가능하다.

하지만 주의를 기울이지 않으면 자기도 모르게 내부를 수정하도록 허락하는 경우가 생긴다.

예컨대 기간을 표현하는 다음 클래스는 한번 값이 정해지면 변하지 않도록 할 생각이었다.

public final class Period `{`
	private final Date start;
	private final date end;
`}`

얼핏 이 클래스는 불변처럼 보이지만, Date가 가변이라는 사실을 이용하면 어렵지 않게 그 불변식을 깨뜨릴 수 있다.

Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
end.setYear(78);

다행히 자바 8 이후로는 쉽게 해결할 수 있다. Date 대신 불변인 Instant를 사용하면 된다.

Date는 낡은 API이니 새로운 코드를 작성할 때는 더 이상 사용하면 안된다.

외부 공격으로부터 Period 인스턴스의 내부를 보호하려면 생성자에서 받은 가변 매개변수 각각을 방어적으로 복사해야 한다.

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) {
		//매개변수 유효성 검사..
	}
`}`

새로 작성한 생성자를 사용하면 앞서의 공격은 더 이상 Period에 위협이 되지 않는다.

매개변수의 유효성을 검사하기 전에 방어적 복사본을 만들고, 이 복사본으로 유효성을 검사한 점에 주목하자

순서가 부자연스러워 보이겠지만 반드시 이렇게 작성해야 한다.

멀티 스레딩 환경이라면 원본 객체의 유효성을 검사한 후 복사본을 만드는 그 찰나의 취약한 순간에 다른 스레드가 원본 객체를 수정할 위험이 있기 때문이다.

방어적 복사를 매개변수 유효성 검사 전에 수행하면 이런 위험에서 해방될 수 있다.

컴퓨터 보안 커뮤니티에서는 이를 검사시점/사용시점 공격 혹은 영어 표기를 줄여서 TOCTOU 공격이라 한다.

방어적 복사에 Date의 clone 메서드를 사용하지 않은 점에도 주목하자.

Date는 final이 아니므로 clone이 Date가 정의한 게 아닐 수 있다.

즉 clone이 악의를 가진 하위 클래스으 인스턴스를 반환할 수도 있다.

예컨대 이 하위 클래스는 start와 end 필드의 참조를 private 정적 리스트에 담아뒀다가 공격자엑세 이 리스트에 접근하는 길을 열어줄 수도 있다.

결국 공격자에게 Period 인스턴스 자체를 송두리째 맡기는 꼴이 된다.

이런 공격을 막기 위해서는 매개변수가 제 3자에 의해 확장될 수 잇는 타입이라면 방어적 복사본을 만들 때 clone을 사용해서는 안된다.

51. 메서드 시그니처를 신중히 설계하라

이번 아이템에는 개별 아이템으로 두기 애매한 API 설계 요령들을 모아 놓았다.

이 요령들을 잘 활용하면 배우기 쉽고, 쓰기 쉬우며, 오류 가능성이 적은 API를 만들 수 있을 것이다.

메서드 이름을 신중히 짓자

항상 표준 명명 규칙을 따라야 한다. 이해할 수 있고, 같은 패키지에 속한 다른 이름들과 일관되게 짓는 게 최우선 목표다.

그 다음 목표는 개발자 커뮤니티에서 널리 받아들여지는 이름을 사용하는 것이다.

긴 이름은 피하자. 애매하면 자바 라이브러리의 API 가이드를 참조하라.

편의 메서드를 너무 많이 만들지 말자.

모든 메서드는 각각 자신의 소임을 다해야 한다.

메서드가 너무 많은 클래스는 익히고, 사용하고, 문서화하고, 테스트하고, 유지보수하기 어렵다.

인터페이스도 마찬가지다. 메서드가 너무 많으면 이를 구현하는 사람과 사용하는 사람 모두를 고통스럽게 한다.

클래스나 인터페이스는 자신의 각 기능을 완벽히 수행하는 메서드로 제공해야 한다.

하주 자주 쓰일 경우에만 별도의 먁칭 메서드를 두기 바란다. 확신이 서지 않으면 만들지 말자

매개변수 목록은 짧게 유지하자

4개 이하가 좋다. 일단 4개가 넘어가면 매개변수를 전부 기억하기가 쉽지 않다.

여러분이 만든 API에 이 제한을 넘는 메서드가 많다면 프로그래머들은 API 문서를 옆에 끼고 개발해야 할 것이다.

IDE를 사용하면 수고를 많이 덜 수 있지만, 여전히 매개변수 수는 적은 쪽이 훨씬 낫다.

같은 타입의 매개변수 여러 개가 연달아 나오는 경우가 특히 해롭다.

사용자가 매개변수 순서를 기억하기 어려울 뿐더러, 실수로 순서를 바꿔 입력해도 그대로 컴파일되고 실행된다. 단지 의도와 다르게 동작할 뿐이다.

과하게 긴 매개변수 목록을 짧게 줄여주는 기술 세 가지를 소개한다.

첫 번째, 여러 메서드로 쪼갠다. 쪼개진 메서드 각각은 원래 매개변수 목록의 부분집합을 받는다.

잘못하면 메서드가 너무 많아질 수 있지만, 직교성을 높여 오히려 메서드 수를 줄여주는 효과도 있다.

  • 직교성이란 “공통점이 없는 기능들이 잘 분리되어 있다” 혹은 “기능을 원자적으로 쪼개 제공한다” 정도로 해석할 수 있다.

매개변수 수를 줄여주는 두 번째 기술은 매개변수 여러 개를 묶어주는 도우미 클래스를 만드는 것이다.

일반적으로 이런 도우미 클래스는 정적 멤버 클래스로 둔다.

특히 잇따른 매개변수 몇 개를 독립된 하나의 개념으로 볼 수 있을 때 추천하는 기법이다.

세 번째는 앞서의 두 기법을 혼합한 것으로, 객체 생성에 사용한 빌더 패턴을 메서드 호출에 응용한다고 보면 된다.

이 기법은 매개변수가 많을 때, 특히 그중 일부는 생략해도 괜찮을 때 도움이 된다.

먼저 모든 매개변수를 하나로 추상화된 객체를 정의하고, 클라이언트에서 이 객체의 세터 메서드를 호출해 필요한 값을 설정하게 하는 것이다.

이때 각 세터 메서드는 매개변수 하나 혹은 서로 연관된 몇 개만 설정하게 한다.

클라이언트는 먼저 필요한 매개변수를 다 설정한 다음, execute 메서드를 호출해 앞서 설정한 매개변수드르이 유효성을 검사한다.

마지막으로 설정이 완료된 객체를 넘겨 원하는 계산을 수행한다.

매개변수의 타입으로는 클래스보다는 인터페이스가 더 낫다

매개변수로 적합한 인터페이스가 있다면 (이를 구현한 클래스가 아닌) 그 인터페이스를 직접 사용하자.

예를 들어 메서드에 HashMap을 넘길 일은 전혀 없다. 대신 Map을 사용하자.

그러면 HashMap 뿐 아니라 TreeMap, ConcurrentHashMap, 등 어떤 Map 구현체도 인수로 건넬 수 있다.

심지어 아직 존재하지 않는 Map도 가능하다.

인터페이스 대신 클래스를 사용하면 클라이언트에게 특정 구현체만 사용하도록 제한하는 꼴이며, 혹시라도 입력 데이터가 다른 형태로 존재한다면 명시적 특정 구현체의 객체로 옮겨 담느라 비싼 복사 비용을 치러야 한다.

또한 boolean 보다는 원소 2개짜리 열거 타입이 낫다(메서드 이름상 boolean을 받아야 의미가 더 명확할 때는 예외다.)

열거 타입을 사용하면 코드를 읽고 쓰기가 더 쉬워진다.

나중에 선택지를 추가하기도 쉽다.

52. 다중정의는 신중히 사용하라

다음은 컬렉션을 집합, 리스트, 그 외로 구분하고자 만든 프로그램이다.

public class CollectionClassfier `{`
	public static String classify(Set`<?>` s) {
		return "집합";
	}

	public static String classify(List`<?>` l) {
		return "리스트";
	}

	public static String classify(Collection`<?>` c) {
		return "그 외";
	}

	psvm(String[] args) `{`
		Collection`<?>`[] collections = `{`
			new HashSet`<String>`(),
			new ArrayList`<String>`(),
			new HashMap`<String, String>`().values(),
		`}`;
		
		for(Collection`<?>` c : collections) `{`
			System.out.println(classify(c)
		`}`
	}
}

“집합”. “리스트”, “그 외”를 차례로 출력할 것 같지만, 실제로 수행해보면 “그 외”만 세 번 연달아 출력된다.

이유가 뭘까? 다중정의(오버로딩)된 세 classify 중 어느 메서드를 호출할지가 컴파일타임에 정해지기 때문이다.

컴파일타임에는 for 문 안의 c 는 항상 Collection<?> 타입이다.

런타임에는 타입이 매번 달라지지만, 호출할 메서드를 선택하는 데는 영향을 주지 못한다.

따라서 컴파일타임의 매개변수 타입을 기준으로 항상 세 번째 메서드인 classify(Collection<?>)만 호출하는 것이다.

이처럼 직관과 어긋나는 이유는 재정의한 메서드는 동적으로 선택되고, 다중정의한 메서드는 정적으로 선택되기 때문이다.

메서드를 재정의했다면 해당 객체의 런타임 타입이 어떤 메서드를 호출할지의 기준이 된다.

모두 알다시피, 메서드 재정의란 상위 클래스가 정의한 것과 똑같은 시그니처의 메서드를 하위 클래스에서 다시 정의한 것을 말한다.

메서드를 재정의한 다음 “하위 클래스의 인스턴스”에서 그 메서드를 호출하면 재정의한 메서드가 실행된다.

컴파일 타임에 그 인스턴스의 타입이 무엇이었냐는 상관없다.

위 코드의 CollectionClassifier 예에서 프로그램의 원래 의도는 매개변수의 런타임 타입에 기초해 적절한 다중정의 메서드로 자동 분배하는 것이었다.

하지만 다중정의는 이렇게 동작하지 않는다.

이 문제는(정적 메서드를 사용해도 좋다면) CollectionClassifier의 모든 classify 메서드를 하나로 합친 후 instanceof로 명시적으로 검사하면 말끔히 해결된다.

public static String classify(Collection`<?>` c) `{`
	return c instanceof Set ? "집합" : 
				c instanceof List ? "리스트" : "그 외";
`}`

프로그래머에게는 재정의가 정상적인 동작 방식이고, 다중정의가 예외적인 동작으로 보일 것이다.

즉, 재정의한 메서드는 프로그래머가 기대한 대로 동작하지만, CollectionClassifier 예에서처럼 다중정의한 메서드는 이러한 기대를 가볍게 무시한다.

헷갈릴 수 있는 코드는 작성하지 않는 게 좋다. 특히나 공개 API라면 더욱 신경 써야 한다.

API 사용자가 매개변수를 넘기면서 어떤 다중정의 메서드가 호출될지를 모른다면 프로그램이 오동작하기 쉽다.

런타임에 이상하게 행돌할 것이며 API 사용자들은 문제를 진단하느라 긴 시간을 허비할 것이다.

그러니 다중정의가 혼동을 일으키는 상황을 피해야 한다.

안전하고 보수적으로 가려면 매개변수 수가 같은 다중정의는 만들지 말자.

가변인수를 사용하는 메서드라면 다중정의를 아예 하지 말아야 한다. 이 규칙만 잘 따르면 어떤 다중 정의 메서드가 호출될지 헷갈릴 일은 없을 것이다.

특별히 따르기 어려운 규칙은 아닐 것으로 믿는다.

다중정의하는 대신 메서드 이름을 다르게 지어주는 길도 항상 열러 있으니 말이다.

한편, 생성자는 이름을 다르게 지을 수 없으니 두 번째 생성자부터는 무조건 다중정의가 된다.

하지만 정적 팩터리라는 대안을 활용할 수 있는 경우가 많다.

또한 생성자는 재정의할 수 없으니 다중정의와 재정의가 혼용될 걱정은 넣어둬도 된다.

그래도 여러 생성자가 같은 수의 매개변수를 받아야 하는 경우를 완전히 피해갈 수는 없을 테니, 그럴 때를 대비해 안전 대책을 배워두면 도움이 될 것이다.

매개변수 수가 같은 다중정의 메서드가 많더라도, 그중 어느 것이 주어진 매개변수 집합을 처리할지가 명확히 구분된다면 헷갈릴 일은 없을 것이다.

즉 매개변수 중 하나 이상이 근본적으로 다르다면 헷갈릴 일이 없다.

근본적으로 다르다는 건 두 타입의 (null이 아닌) 값을 서로 어느 쪽으로든 형변환할 수 없다는 뜻이다.

예컨대 ArrayList에는 int를 받는 생성자와 Collection을 받는 생성자가 있는데, 어떤 상황에서든 두 생성자 중 어느 것이 호출될 지 헷갈릴 일은 없을 것이다.

다중정의된 메서드(혹은 생성자)들이 함수형 인터페이스를 인수로 받을 때, 비록 서로 다른 함수형 인터페이스라도 인수 위치가 같으면 혼란이 생긴다.

따라서 메서드를 다중정의할 때, 서로 다른 함수형 인터페이스라도 같은 위치의 인수로 받아서는 안된다.

53. 가변인수는 신중히 사용하라

가변인수 메서드는 명시한 타입의 인수를 0개 이상 받을 수 있다. 가변인수 메서드를 호출하면, 가장 먼저 인수의 개수와 길이가 같은 배열을 만들고 인수들을 이 배열에 저장하여 가변인수 메서드에 건네준다.

다음 예는 int 인수들의 합을 계산해주는 가변인수 메서드다

static int sum(int... args) {
	int sum = 0;
	for (int arg : args)
		sum += arg;
	return sum;
}

인수가 1개 이상일 때도 있다. 그럴때는

static int sum(int... args) {
	if(arg.length == 0)
		throw new IllegalArgumentException("");
	int sum = 0;
	for (int arg : args)
		sum += arg;
	return sum;
}

이 방식에는 문제가 몇 개 있다. 가장 심각한 문제는 인수를 0개만 넣어 호출하면 (컴파일타임이 아닌) 런타임에 실패한다는 점이다.

코드도 지저분하다. args 유혀성 검사를 명시적으로 해야 한다.

다행히 훨씬 나은 방법이 있다. 다음 코드처럼 매개변수를 2개 받도록 하면 된다.

즉, 첫 번째로는 평범한 매개변수를 받고, 가변인수는 두 번째로 받으면 앞서의 문제가 말끔히 사라진다.

static int sum(int, firstArg, int... remainingArgs) {
	int min = firstArg;
	for (int arg : remainingArgs)
		if(arg<min)
			min += arg;
	return min;
}

이상의 예에서 보듯, 가변인수는 인수 개수가 정해지지 않았을 때 아주 유용하다.

printf 는 가변인수와 한 묶음으로 자바에 도입되었고, 이때 핵심 리플렉션 기능도 재정비되었다.

printf와 리플렉션 모두 가변인수의 덕을 톡톡히 보고있다.

그런데 성능에 민감한 상황이라면 가변인수가 걸림돌이 될 수 있다.

가변인수 메서드는 호출될 때마다 배열을 새로 할당하고 초기화한다.

다행히 이 비용을 감당할 수는 없지만 가변인수의 유연성이 필요할 때 선택할 수 있는 멋진 패턴이 있다.

예를 들어 해당 메서드 호출의 95%가 인수를 3개 이하로 사용한다고 해보자. 그렇다면 다음처럼 인수가 0개인것부터 4개인것까지 총 5개를 다중정의하자.

마지막 다중정의 메서드가 인수 4개 이상인 5%의 호출을 담당하는 것이다.

public void foo() {}
public void foo(int a1,) {}
public void foo(int a1, int a2) {}
public void foo(int a1, int a2, int a3) {}
public void foo(int a1, int a2, int a3, int... rest) {}

따라서 메서드 호출 중 단 5%만이 배열을 생성한다.

대다수의 성능 최적화와 마찬가지로 이 기법도 보통 때는 별 이득이 없지만, 꼭 필요한 특수 상황에서는 사막의 오아시스가 되어줄 것이다.

54. null이 아닌, 빈 컬렉션이나 배열을 반환하라

다음은 주변에서 흔히 볼 수 있는 메서드다

private final List<Cheese> cheesesInStock = ...;

// 매장안의 모든 치즈 목록을 반환한다. 단 재고가 없다면 null을 반환
public List<Cheese> getCheese() {
	return cheeseInStock.isEmpty() ? null
				: new ArrayList<>(cheesesInStock);
}

사실 재고가 없다고 해서 특별히 취급할 이유는 없다. 그럼에도 이 코드처럼 null을 반환하면, 클라이언트는 이 null 상황을 처리하는 코드를 추가로 작성해야 한다.

List<Cheese> chesses = shop.getCheeses();
if (cheeses != null && chesses.contains(Cheese.STILTON))
		System.out.println("goot");

컬렉션이나 배열 같은 컨테이너가 비었을 때 null을 반환하는 메서드를 사용할 때면 항시 이와 같은 방어 코드를 넣어줘야 한다.

클라이언트에서 방어 코드를 빼먹으면 오류가 발생할 수 있다.

실제로 객체가 0개일 가능성이 거의 없는 상황에서는 수년 뒤에야 오류가 발생하기도 한다.

한편 null을 반환하려면 반환하는 쪽에서도 이 상황을 특별히 취급해줘야 해서 코드가 더 복잡해진다.

때로는 빈 컨테이너를 할당하는 데도 비용이 드니 null을 반환하는 쪽이 낫다는 주장도 있다.

하지만 이는 두 가지 면에서 틀린 주장이다.

첫 번째, 성능 분석 결과 이 할당이 성능 저하의 주범이라고 확인되지 않는 한, 이 정도의 성능 차이는 신경 쓸 수준이 못 된다.

두 번째, 빈 컬렉션과 배열은 굳이 새로 할당하지 않고도 반환할 수 있다.

다음은 빈 컬렉션을 반환하는 전형적인 코드로, 대부분의 상황에서는 이렇게 하면 된다.

public List<Cheese> getCheeses() {
	return new ArrayList<>(cheeseInStock);
}

가능성은 작지만, 사용 패턴에 따라 빈 컬렉션 할당이 성능을 눈에 띄게 떨어뜨릴 수 있다.

다행히 해법은 간단하다. 다음과 같은 불변 컬렉션을 반환하는 것이다. 알다시피 불변 객체는 자유롭게 공유해도 안전하다.

public List<Cheese> getCheeses() {
	return cheeseInStock.isEmpty() ? Collection.emptyList()
			: new ArrayList<>(cheeseInStock);
}

배열을 쓸 때도 마찬가지다. 절대 null을 반환하지 말고 길이가 0인 배열을 반환하라.

성능이 걱정된다면 불변인 빈 배열을 생성해두고 공유해서 반환해도 된다.

55. 옵셔널 반환은 신중히 하라

자바 8 전에는 메서드가 특정 조건에서 값을 반환할 수 없을 때 취할 수 있는 선택지가 두 가지 있었다.

예외를 던지거나, null을 반환하는 것이다.

두 방법 모두 허점이 있다. 예외는 진짜 예외적인 상황에서만 사용해야 하며, 예외를 생성할 때 스택 추적 전체를 캡처하므로 비용도 만만치 않다.

null을 반환하면 이런 문제가 생기지 않지만, 그 나름의 문제가 있다. null을 반환할 수 있는 메서드를 호출할 때는, (null이 반환될 일이 절대 없다고 확신하지 않는 한) 별도의 null 처리 코드를 추가해야 한다.

null 처리를 무시하고 반환된 null 값을 어딘가에 저장해두면 언젠가 NPE가 발생할 수 있다.

그것도 근본적인 원인, 즉 null을 반환하게 한 실제 원인과는 전혀 상관없는 코드에서 말이다.

자바 버전이 8로 올라가면서 또 하나의 선택지가 생겼다.

그 주인공인 Optional<T>는 null이 아닌 T타입 참조를 하나 담거나, 혹은 아무것도 담지 않을 수 있다.

아무것도 담지 않은 옵셔널은 ‘비었다’고 말한다. 반대로 어떤 값을 담은 옵셔널은 ‘비지 않았다’고 한다.

옵셔널은 원소를 최대 1개 가질 수 있는 ‘불변’ 컬렉션이다. Optional이 Collection을 구현하지는 않았지만, 원칙적으로 그렇다는 말이다.

보통은 T를 반환해야 하지만 특정 조건에서는 아무것도 반환하지 않아야 할 때 T 대신 Optional<T>를 반환하도록 선언하면 된다.

그러면 유효한 반환값이 없을 때는 빈 결과를 반환하는 메서드가 만들어진다.

Optional을 반환하고 싶으면 적절한 정적 팩터리를 사용하여 반환하면 된다.

return Optional.empty(); // 빈 옵셔널
return Optional.of(result); // 값이 든 옵셔널

Optional.of(value)에 null을 넣으면 NPE를 던지니 주의하자.

null 값도 허용하는 옵셔널을 만들려면 Optional.ofNullable(value)를 사용하면 된다.

옵셔널을 반환하는 메서드에서는 절대 null을 반환하지 말자.

옵셔널을 도입한 취지를 완전히 무시하는 행위다.

반환값으로 옵셔널을 사용한다고 해서 무조건 득이 되는 건 아니다.

컬렉션, 스트림, 배열, 옵셔널 같은 컨테이너 타입은 옵셔널로 감싸면 단된다.

Optional<List<T>>를 반환하기보다는 빈 List<T>를 반환하는 게 좋다.

빈 컨테이너를 그대로 반환하면 클라이언트에 옵셔널 처리 코드를 넣지 않아도 된다.

그렇다면 어떤 경우에 메서드 반환 타입을 T 대신 Optional<T>로 선언해야 할까?

기본 규칙은 이렇다. 결과가 없을 수 있으며, 클라이언트가 이 상황을 특별하게 처리해야 한다면 Optional<T>를 반환한다.

박싱된 기본 타입을 담는 옵셔널은 기본 타입 자체보다 무거울 수 밖에 없다.

값을 두 겹이나 감싸기 때문이다.

그래서 자바 API 설계자는 기본 타입 전용 옵셔널인 OptionalInt, OptionalLong 등을 준비해놨다.

이렇게 대체재까지 있으니 박싱된 기본 타입을 담은 옵셔널을 반환하는 일은 없도록 하자.

옵셔널을 맵의 값으로 사용하면 절대 안된다. 만약 그리 한다면 맵 안에 키가 없다는 사실을 나타내는 방법이 두 가지가 된다.

하나는 키 자체가 없는 경우고, 다른 하나는 키는 있지만 그 키가 속이 빈 옵셔널일 경우다.

쓸데없이 복잡성만 높여서 혼란과 오류 가능성을 키울 뿐이다.

더 일반화해 이야기하면 옵셔널을 컬렉션의 키, 값, 원소나 배열의 원소로 사용하는 게 적절한 상황은 거의 없다.

56. 공개된 API 요소에는 항상 문서화 주석을 작성하라

API 문서를 편하게 작성하게 도와주는 자바독이라는 유틸리티가 존재한다.

자바 프로그래머라면 이 자바독의 작성법을 잘 알아야 한다.

여러분의 API를 올바로 문서화하려면 공개된 모든 클래스, 인터페이스, 메서드, 필드 선언에 문서화 주석을 달아야 한다.

직렬화 할 수 있는 클래스라면 직렬화 형태에 관해서도 적어야 한다.

메서드용 문서화 주석에는 해당 메서드와 클라이언트 사이의 규약을 명료하게 기술해야 한다.

문서화 주석은 여러분 API를 문서화하는 가장 훌륭하고 효과적인 방법이다. 공개 API라면 빠짐없이 설명을 달아야 한다.

표준 규약을 일관되게 지키자. 문서화 주석에 임의의 HTML 태그를 사용할 수 있음을 기억하라. 단, HTML 메타문자는 특별하게 취급해야 한다.