충돌 대신 예외 잡기
무언가 잘못되면 Java가 예외를 던지고, 처리되지 않은 예외는 스택 트레이스와 함께 프로그램을 멈춘다는 것은 이미 알고 있을 것입니다. try-catch 블록은 제어권을 가져오는 방법입니다. 위험한 코드를 try로 감싸면, 예외가 던져지더라도 Java는 충돌하는 대신 일치하는 catch 블록으로 점프합니다.
10 / 0이 예외를 던지는 순간, try 블록의 나머지는 건너뛰고 제어권이 catch로 넘어갑니다. catch가 끝나면 실행은 정상적으로 계속됩니다. 프로그램은 죽지 않습니다.
catch 변수는 예외를 담는다
catch (ArithmeticException e)의 e는 실제 객체입니다. 무엇이 잘못되었는지에 대한 정보를 담고 있으며, 가장 유용한 것은 메시지입니다.
e.getMessage()는 짧은 설명을 반환합니다. 디버깅 중에는 e.printStackTrace()가 예외가 정확히 어디서 왔는지 보여 주는 전체 트레이스를 출력합니다. 메시지만으로 원인을 찾기에 부족할 때 사용하세요.
전부가 아니라 구체적인 타입을 잡아라
catch 블록은 선언된 타입(또는 그 하위 클래스)과 일치하는 예외만 잡습니다. 초보자가 저지르는 가장 큰 실수는 문제를 "사라지게" 하려고 Exception을 잡는 것입니다.
// 이렇게 하지 마세요 - 진짜 버그를 숨깁니다
try {
doWork();
} catch (Exception e) {
// NullPointerException, 오타, 로직 오류... 전부를 삼킨다
}
실제로 처리할 수 있는 가장 좁은 타입을 잡으세요. 잘못된 숫자 입력이 예상된다면 NumberFormatException을 잡으세요. 예상하지 못한 것은 전파되도록 두어, 망가진 상태로 조용히 계속 진행하는 대신 실제로 그것을 알아챌 수 있게 해야 합니다.
여러 예외 타입 처리하기
여러 개의 catch 블록을 쌓을 수 있습니다. Java는 위에서 아래로 검사하여 일치하는 첫 번째 블록을 실행하므로, 가장 구체적인 것부터 가장 일반적인 것 순으로 정렬하세요.
두 타입이 같은 처리를 공유한다면, 블록을 복제하는 대신 |를 사용한 하나의 멀티 캐치를 사용하세요.
함정: 더 일반적인 타입은 구체적인 타입 뒤에 와야 합니다. catch (Exception e)를 먼저 두면 뒤따르는 더 구체적인 블록이 도달 불가능해지고, 컴파일러가 이를 거부합니다.
finally는 항상 실행된다
finally 블록은 성공이든, 잡힌 예외든, 심지어 이른 return이든 무슨 일이 일어났는지에 관계없이 try와 모든 catch 이후에 실행됩니다. 항상 일어나야 하는 정리 작업은 여기에 둡니다.
예외가 발생하든 안 하든 "Closing resource"가 출력됩니다. 다만 finally 내부에서 return하는 것은 피하세요. 예외를 조용히 버리거나 try 블록에서 반환된 값을 덮어쓸 수 있습니다.
try-with-resources가 대신 닫아 준다
닫아야 하는 무언가(파일, 네트워크 연결, 데이터베이스 구문)를 다룰 때, 그것을 try (...) 안에서 선언하면 예외가 던져지더라도 블록이 끝날 때 자동으로 닫힙니다. AutoCloseable 인터페이스를 구현한 모든 타입에서 동작합니다.
이것은 try에서 열고 finally에서 닫던 옛 패턴을 대체하며, 닫는 것을 잊을 수 없기 때문에 오류가 덜 발생합니다. 닫을 수 있는 리소스를 다룰 때는 언제나 이 방식을 선호하세요.
일반적인 제어 흐름에 예외를 사용하지 마라
try-catch는 평범한 조건이 아니라 예외적인 상황을 위한 것입니다. 예외를 잡는 것은 단순한 검사보다 비용이 크고, 코드를 읽기 어렵게 만듭니다. 조건을 먼저 검사할 수 있다면 그렇게 하세요.
// 피하기: 없는 키를 검사하려고 catch 사용
try {
process(map.get(key).trim());
} catch (NullPointerException e) {
// 없는 키 처리
}
// 선호하기: 명시적으로 검사
String value = map.get(key);
if (value != null) {
process(value.trim());
}
catch는 진정으로 통제 밖에 있는 것들, 즉 잘못된 사용자 입력, 없는 파일, 네트워크 장애를 위해 남겨 두세요.
다음: NullPointerException
Java에서 당신이 잡게 될(그리고 일으키게 될) 가장 흔한 예외는 NullPointerException입니다. null로 밝혀진 무언가에 메서드를 호출하는 순간 나타납니다. 다음에는 정확히 무엇이 그것을 유발하는지, 스택 트레이스를 어떻게 읽는지, 그리고 애초에 그것이 발생하지 않게 하는 습관들을 자세히 파헤쳐 보겠습니다.
자주 묻는 질문
Java에서 try-catch는 어떻게 사용하나요?
예외를 던질 수 있는 코드를 try 블록 안에 넣고, 처리하려는 예외 타입을 지정한 catch 블록을 추가하세요: try { risky(); } catch (IOException e) { ... }. try 안의 코드가 일치하는 예외를 던지면, Java는 충돌하는 대신 곧바로 catch 블록으로 점프합니다. 변수(e)는 예외 객체를 담고 있으므로 e.getMessage()로 메시지를 읽을 수 있습니다.
Java에서 finally 블록은 무엇을 위한 것인가요?
finally 블록은 코드가 성공했든, 예외를 던졌든, 심지어 일찍 return했든 무슨 일이 있어도 try/catch 이후에 실행됩니다. 파일을 닫거나 잠금을 해제하는 등 항상 일어나야 하는 정리 작업을 두는 곳입니다. 리소스를 닫는 경우에는 자동으로 닫아 주는 try-with-resources가 보통 더 깔끔합니다.
Exception을 잡아야 하나요, 아니면 특정 예외 타입을 잡아야 하나요?
실제로 처리할 수 있는 가장 구체적인 타입을 잡으세요. Exception(더 나쁘게는 Throwable)을 잡으면 NullPointerException 같은 버그를 포함한 모든 문제를 삼켜 버려 진짜 원인을 숨깁니다. 기대하는 것이 그것이라면 NumberFormatException을 잡고, 예상치 못한 예외는 전파되도록 두어 그것을 알아챌 수 있게 하세요.