오류 및 예외에 대한 이해
오류(Error) vs 예외(Exception)
오류(Error)란?
- 일반적으로 회복이 불가능한 문제
- 시스템 레벨에서, 또는 주로 환경적인 이유로 발생
- 코드의 문제로 발생하는 경우도 있지만, 일단 발생하는 경우 일반적으로 회복이 불가능
- 에러가 발생한 경우 우리는 어떠한 에러로 프로그램이 종료되었는지를 확인하고 대응해야 함
예외(Exception)란?
- 일반적으로 회복이 가능한 문제
- 회복이 가능하다는 전제는 우리가 “그 예외가 발생할 수 있다는 것을 인지하고, 대응했을 것
- 현실적으로 코드레벨에서 할 수 있는 문제상황에 대한 대응은 “예외처리”에 속함.
예외 클래스 구조 : 자바의 Throwable Class
- 모든 객체의 원형인
Object 클래스에서 시작
Object클래스 밑에는 문제 상황을 뜻하는 Throwable클래스가 Object클래스를 상속
Throwable클래스는 모든 문제 상황을 다 포함하는 클래스
Throwable 클래스의 자식으로 에러(Error)와 예외(Exception) 클래스가 존재
에러(Error) 클래스와 예외(Exception) 클래스는 각각 IOError 클래스, RuntimeException 클래스와 같이 구분하여 처리된다.


예외의 종류
컴파일 에러(예외)
.java 파일을 .class 파일로 컴파일할때 발생하는 에러
- 대부분 자바 프로그래밍 언어의 규칙을 지키지 않았기 때문에 발생
ex. 있지 않은 클래스를 호출한다거나, 접근이 불가능한 프로퍼티나 메소드에 접근한다거나 하는 경우에 발생
- 해결방법
런타임 에러(예외)
- 문법적인 오류는 아니라서, 컴파일은 잘 되었지만 “프로그램”이 실행도중 맞닥뜨리게 되는 예외
예외 처리 관점에서 예외의 종류
- 확인된 예외(Checked Exception)
- 예외가 발생하는 상황을 인지하고, 어떠한 에러인지 미리 정의를 해놨기 때문에 컴파일 에러와 다름
- 컴파일 하는동안 이 예외에 대한 예외처리를 했는지 확인할 수 있는 예외
- 미확인된 예외(Unchecked Exception)
- 런타임 시점에 확인되는 예외
- 예외 처리가 반드시 필요하지 않은 예외
- 컴파일 하는동안 이 예외에 대한 예외처리를 했는지 확인할 수 없는 예외
예외 처리하기
1. 예외 정의하기
- 예외 클래스를 만들어 발생할 수 있는 예외를 미리 정의
OurBadException : 발생이 예상되는 예외 사항
class OurBadException extends Exception {
public OurBadException() {
super("위험한 행동을 하면 예외처리를 꼭 해야합니다!");
}
}
2. 예외가 발생할 수 있음 알리기 : Throw, Throws
throws
- 메서드 이름 뒤에 위치
- 메서드가 어떠한 예외사항을 던질 수 있는지 알려주는 예약어
- 여러 종류의 예외사항을 적을 수 있다.
throw
- 메서드 안에 위치
- 실제로 예외 객체를 던질 때 사용하는 예약어
- 실제로 던지는 예외 객체 하나와 같이 사용
- return 키워드처럼 throw아래의 구문들은 실행되지 않고 메서드가 종료됨.
예시
// OurClass.java
class OurClass {
private final Boolean just = true;
public void thisMethodIsDangerous() throws OurBadException { // throws : 예외 확인
if (just) {
throw new OurBadException(); // throw : 새로운 예외 객체를 만들어서 던짐
}
}
}
3. 예상되는 예외 다루기 : try - catch - finally
- 이미 OurClass.java에서 throw를 통해서 thisMethodIsDangerous()가 예외를 발생시킬 수 있다고 정의했기 때문에 try-catch-finally 구분을 사용해야 한다
// StudyException.java
// 일단 try해
// ~~~그리고 예외가 발생하면 그걸 잡아(catch)
// 그리고, 정식적으로 수행되든, 예외가 발생하든 결국, 마침내 수행돼야 하는 로직을 'finally' 수행해!
public class StudyException {
public static void main(String[] args) {
OurClass ourClass = new OurClass();
try {
// 1. 위험한 메소드의 실행을 "시도" 해 봅니다.
// "시도" 해보는 코드가 들어가는 블럭입니다.
ourClass.thisMethodIsDangerous();
} catch (OurBadException e) {
// 2. 예외가 발생하면, "잡아서" handling 합니다.
// 예외가 발생하는경우 "handling" 하는 코드가 들어가는 블럭입니다.
// 즉 try 블럭 내의 구문을 실행하다가 예외가 발생하면
// 예외가 발생한 줄에서 바로 코드 실행을 멈추고
// 여기 있는 catch 블럭 내의 코드가 실행됩니다.
System.out.println(e.getMessage());
} finally {
// 3. 예외의 발생 여부와 상관없이, 실행시켜야 하는 코드가 들어갑니다.
// 무조건 실행되는 코드가 들어가는 블럭입니다.
System.out.println("우리는 방금 예외를 handling 했습니다!");
}
}
}
연결된 예외(Chained Exception)란
- 예외의 원인이 연결되어 있어 한 예외가 다른 예외를 유발할 수 있는 경우를 말함
- A의 예외 원인은 B에 있고, B의 예외 원인이 C에 있는 경우 연결된 예외 상황이라고 볼 수 있다.
- 예외 A가 예외 B를 발생시켰다면, 예외 A는 B의 원인 예외가 된다.
정의
- 원인 예외가 새로운 예외를 발생시키는 경우를
예외 연결이라 한다.
원인 예외를 다루기 위한 메소드
initCause() : 지정한 예외를 원인 예외로 등록하는 메소드
getCause() : 원인 예외를 반환하는 메소드
예외 처리하기
방법 1. 예외 복구
- 실제로 try-catch로 예외를 처리하고 프로그램을 정상 상태로 복구하는 방법
- 만약 오류가 생긴다면, 오류사항으로 인해 프로그램을 종료하는 대신 default값을 반환하는 형식으로 정상상태로 복구 시도
- 가장 기본적인 방식이지만, 현실적으로 복구가 가능한 상황이 아닌 경우가 많거나 최소한의 대응만 가능한 경우가 많기 때문에 자주 사용하는 방법은 아님
public String getDataFromAnotherServer(String dataPath) {
try {
return anotherServerClient.getData(dataPath).toString();
} catch (GetDataException e) {
return defaultData;
}
}
방법 2. 예외 처리 회피
- ex.
someMethod()에서 발생한 에러가 someIrresponsibleMethod()의 throws를 통해서 그대로 다시 흘러나가게하여 예외 처리 회피.
- 물론 같은 객체내에서 이런일은 하지는 않음, 예외처리 회피를 보이기 위한 단순한 예시 코드
- 관심사를 분리해서 한 레이어에서 처리하기 위해서 이렇게 에러를 회피해서 그대로 흘러 보내는 경우도 있다.
public void someMethod() throws Exception { ... }
public void someIrresponsibleMethod() throws Exception {
this.someMethod();
}
방법 3. 예외 전환
- 예외처리 회피하기의 방법과 비슷하지만, 조금더 적절한 예외로 다시 던져주는 경우
- 보통은 예외처리에 더 신경쓰고싶은 경우나, 오히려
RuntimeException처럼 일괄적으로 처리하기 편한 예외로 바꿔서 던지고 싶은 경우 사용
public void someMethod() throws IOException { ... }
public void someResponsibleMethod() throws MoreSpecificException {
try {
this.someMethod();
} catch (IOException e) {
throw new MoreSpecificException(e.getMessage());
}
}