본문 바로가기

Dev.BackEnd/JAVA

[JAVA] 10. 예외처리(Exception Handling)




Chapter 10. 예외처리 (Exception Handling)

코드를 작성하는 과정에서 예기지 못한 수많은 에러들이 발생한다.

컴파일 전에 알게 되는 에러도 있고,

작성할 때는 아무 문제 없다가 실행시키고 나서야 발생하는 에러들이 있다.

이것들을 제어할 수 있어야 좀 더 안전한 프로그램을 설계할 수 있게 된다.


에러 (Error) ? 예외 (Exception) ?
컴파일 에러 - 컴파일 시에 발생하는 에러
런타임 에러 - 실행 시에 발생하는 에러
논리적 에러 - 실행은 되지만, 의도와 다르게 동작하는 에러

대부분의 컴파일 에러는 이클립스 같은 IDE들이 빨간줄 표시로 잡아준다.
문제는 실행 시 발생하는 프로그램 오류다.
자바에서는 이 Runtime 오류를 에러와 예외, 두 가지로 나누어 인식한다.
에러(error)
메모리 부족, 스택오버플로우와 같이 일단 발생하면 복구할 수 없는,
그렇기 때문에 프로그램의 비정상적인 종료를 막을 수 없는 것을 말한다.
예외(Exception)
발생하더라도 수습될 수 있는, 비교적 덜 심각한,
그렇기에 프로그램의 비정상적인 종료를 사전에 예방할 수 있는 것을 말한다.

자바에서 예외처리는 이 Exception을 Handling하는 것을 말한다.
프로그램을 제작하는 과정에서 발생할 예외에 대해 미리 코드를 작성하는 것이다.
if-else 문으로 처리를 해야만 했던, C에서는 예외처리를 위한 if 문과 일반 if문을 구분하기 힘들었다.
이 문제를 해결하기 위해 JAVA에서는 예외처리에 특화된 문법을 제공한다.

try-catch 구문
예외를 발생시킬 위험을 갖고 있는 코드인 경우 try{ } 안에 넣어둔다.
그리고 catch를 통해 오류 메시지를 출력한다.
try안에 있는 코드를 실행하다가 오류가 발생하면 catch 구문으로 Jump 한다.
(Jump! 즉, 오류가 발생한 구문 다음의 코드들은 실행되지 않는다!)
그러면 catch 메소드가 실행되게 되는데,
이 때 Exception 이라는 클래스를 e로 인스턴스화 하여 catch에 매개변수로 전달한다.
그리고 Exception 이라는 클래스에 있던 getMessage라는 메소드를 호출(or 다른 구문)한다.
catch 구문에 대한 내용이 끝이나면 프로그램을 종료하는 것이 아니라 하던 컴파일 계속 해나간다.

try{
     //예외의 발생이 예상되는 로직 -> 이 로직은 예외상황과 관련있는 문장들도 고려해야 한다.
} catch (예외클래스 인스턴스){
     //예외가 발생했을 때 실행되는 로직
}
예외가 발생했을 때 실행되는 로직의 예이다.
System. out. println(e.getMessage ()); //가장 간단한 예외 상황을 출력
System. out. println(e.toString ()); //예외 상황에 대한 좀 더 자세한 정보를 출력
e. printStackTrace(); // 예외 상황이 발생한 소스코드의 위치까지 출력
각각 상황에 따라 사용한다.
catch 구문을 여러개 두면 다중  catch도 가능하다.
+ 웹 프로그래밍에서는 사용자에게 오류가 발생했다는 것을 조금 부드럽게 전달하기 위해 
Error Page를 따로 만들어서 redirect 시킨다!

try - catch - finally
finally라는 구문이 추가되었다. 
finally 영역은 try영역으로 일단 들어가면 무조건 실행되는 영역이다.
finally는 항상 try - catch 다음에 위치해야 한다.
finally를 언제 쓰는가? 웹 프로그래밍의 예를 들어보겠다.
자바 애플리케이션들이 데이터베이스에 접속하여 데이터를 가져온다.
DB가 수용할 수 있는 애플리케이션의 수에는 한계가 존재한다.
try안에 DB와 관련된 Exception을 넣어준다.
연결이 되어서 작업을 모두 마쳤다면 연결을 끊어줘야 한다.
이 때 finally를 사용하여 DB와 접속한 것을 해제해준다.(free ?) 
try{
  ServletContext sc = this.getServletContext();
  Class.forName(sc.getInitParameter("driver"));
  Connection conn = DriverManager.getConnection(...
    ...)
} catch (Exception e) {
  throw e;
} finally{
  try {if (stmt != null) stmt.close();} catch(Exception e) {}
}
예시코드는 Servlet에서 가져왔다.
극히 일부분이니 예외처리를 이해하는 용도로만 받아들이면 되겠다.
위 예시 코드에서 finally 구문에 try-catch 문이 또 쓰인 것을 볼 수 있다.
일단 코드가 복잡해져서 가독성이 떨어지게 되고,
try 블럭과 finally 블럭에서 모두 예외가 발생하게 되면 try블럭의 예외는 무시된다는 문제점이 있다.
이러한 문제를 해결하기 위해 try-with-resources 구문이 등장하였다.
try 블럭을 작성하기 전에 try ( )를 추가해주는 것이다.
괄호 ( )안에서 객체를 생성하게 되면 close( )메소드를 따로 호출할 필요가 없어진다.
단 생성하려는 객체의 클래스가 AutoCloseable 인터페이스를 구현하고 있어야 한다.


사실 예외처리에서 더 중요한 부분은 throw 부분이다.
예외를 메소드 선언할 때 선언하여, 발생할 예외를 미리 방지하는 방법이다.
사실 이 방법은, 자신을 호출한 메서드에게 예외를 '던져' 예외처리를 떠맡기는 것이다.
예외를 전달받은 메서드가 또 다시 자신을 호출한 메서드에게 전달할 수 있으며,
아무도 예외를 처리하지 않으면 main 메소드에게 까지 던져진다.
이렇게 되면 프로그램이 종료된다.

class B{
    void run(){
    }
}
class C{
    void run(){
        B b = new B() ;
        b. run() ;
    }
}
public class ThrowExceptionDemo {
    public static void main( String[] args) {
         C c = new C ( );
         c .run ( );
    }
}

code 설명>
(최초 생성자) B -> C -> Main -> End User
B에서 발생한 예외 상황을 C에게 던진다.
C는 Main에게 Throw한다
Main도 End User에게 Throw 한다.(프로그램을 종료한다.)
Throw란 예외에 대한 책임을 다음 (사용자)에게 넘기는 것이다.
사용자란 상위 메소드라고 생각해볼수 있다.
결국 어느 한 곳에서는 반드시 try-catch 구문으로 예외처리를 해주어야 한다.
이러한 관점에서 Catch 는 던져지는 예외를 잡아 자신이 예외를 처리하는 것이라고 볼 수 있다. 



포스팅 내용은 생활코딩의 이고잉님의 강의자료를 기준으로 작성하였습니다. 문제가 될 시 삭제하겠습니다.

Chapter 10. The End