이 코드는 배열의 원소를 순회하는데, 배열의 끝에 도달해 ArrayIndexOutOfBoundsException이 발생하면 끝난다.
//예외를 이용한 loop public void optimizeLoop() { Mountain range[] = new Mountain[10]; try { int i = 0; while (true) { range[i++].climb(); } } catch (Exception e) { } }
아마도 표준적인 관용구대로 작성했다면 모든 자바 프로그래머가 이해했을 것이다.
//정상적인 loop
public void optimizeLoop2() {
Mountain range[] = new Mountain[10];
for (Mountain i : range) {
i.climb();
}
}
그런데 왜 예외를 써서 루프를 종료했을까?
이는 잘못된 추론을 근거로 성능을 높이려 한 것이다.
JVM은 배열에 접근할 떄마다 경계를 넘지 않는지 검사한다.
그래서 일반적인 반복문도 배열 경계에 도달하면 종료한다.
따라서 이 검사를 반복문에도 명시하면 같은 일이 중복되므로 하나를 생략한 것이다.
따라서 첫 번째 케이스는 잘못된 케이스이다.
1. 예외는 예외 상황에 쓸 용도로 만들어졌기 때문에 JVM 입장에서는 빠르게 만들 이유가 없다.
2. 코드를 try-catch 블록 안에 넣으면 JVM이 적용할 수 있는 최적화가 제한된다.
3. 배열을 순회하는 표준 관용구는 중복 검사를 수행하지 않는다.(JVM이 알아서 최적화해 없애준다.)
따라서 이 경우에는 예외를 사용한 쪽이 표준 관용구보다 느리다.
절대로 제어 흐름용도로 사용하면 안 된다.
이 원칙은 API 설계에도 적용된다.
for (Iterator<Foo> i = collection.iterator(); i.hasNext(); ) {
Foo foo = i.next();
}
```java
try{
Iterator<Foo> i = collection.iterator();
while(true){
Foo foo = i.next();
...
}
}catch(NoSuchElementException e){
}
하지만, 반복문에서 예외를 사용하면 속도도 느리고, 버그를 숨기기도 한다.
그럼, 상태 검사 메서드, Optional, null 중 하나를 선택하는 지침을 봐보자
1. 외부 동기화 없이 여러 스레드가 동시에 접근할 수 있거나 외부 요인으로 상태가 변할 수 있다면 optional, 특정 값을 사용한다.
=> 상태 검사 메서드와 상태 의존적 메서드 호출 사이에 객체의 상태가 변경될 수 있기 때문
2. 성능이 중요한 상황에서 상태 검사 메서드가 상태 의존적 메서드의 작업을 일부 수행한다면 Optional, 특정값을 반환
3. 다른 모든 경우엔 상태 검사 메서드가 낫다.
만약, 상태 검사 메서드 호출을 깜빡 잊었다면 상태 의존적 메서드가 예외를 던져 버그를 확실히 드러낸다.
반면, 특정 값은 검사하지 않고 지나쳐도 발견하기가 어렵다
예외는 예외 상황을 쓸 의도로만 설계되었다.
따라서 정상적인 제어 흐름에서 사용해서는 안 된다.