2020-09-14 TIL ing
예외처리문법
예외 처리 문법 적용 전
- 계산기의 경우 유효하지 않은 연산자를 지정했을 대 제대로 계산을 수행했는지 검사히기 위해 보통 리턴 값을 검사한다.
- 하지만 정상적인 계산 결과가 유효하지 않은 리턴 값으로 결정된 값과 일치한다면 정상적인 계산 결과도 오류로 취급할 수 있다.
- 이를 방지하기 위해 오류가 발생하면 희귀한 값을 리턴하는 방식을 사용했다. 하지만 이 역시 희귀한 값이 정상적인 리턴 값일 가능성이 발생한다.
- 결국 리턴 값을 검사하여 오류 여부를 처리하는 방식으로는 이런 문제를 해결할 수 없다.
- 리턴 값으로 작업 오류를 알려주는 방식의 한계를 극복하기 위해 만든 문법이 예외처리 문법이다.
- 예외 상황이 발생했을 때 호출자에게 예외 정보를 던져주는 문법이다.
- 또한, 계산할 수 없는 예외 상황이 발생했을 경우 JVM이 실행을 종료하지 않고 정상적으로 실행하게 만드는 문법이 예외 처리이다.
예외처리
개념
- 메서드를 실행하는 중에 예외가 발생했을 때 호출자에게 알려주는 문법
- 메서드를 호출하는 중에 예외를 받았을 때 처리하는 문법
- 예외처리 문법의 의미
- 메서드 실행 중에 예외 상황을 만났을 때 리턴 값을 ㅗ알려주는 방식의 한계를 극복하기 위해
- 예외가 발생하더라도 시스템을 멈추지 않고 적절한 조치를 취한 후 계속 실행하기 위해
예외를 호출자에게 알려주는 문법
- throw [Throwable 객체];
- throw new String(“에외가 발생했습니다”); <- 컴파일 오류
예외를 받았을 때 처리하는 문법
- try { m(); } catch (RuntimeException e) { System.out.println(e.getMessage()); } System.out.println(“시스템을 종료합니다.”);
- catch 문 안에 들어가는 파라미터는 java.lang.throwable과 그 자식들이다.
- object는 들어갈 수 없다.
- 예외가 발생하면 catch 블럭이 실행된다.
예외를 받지 않을때
- 즉시 현재 메서드의 실행을 멈추고 호출자에게 예외 처리를 위임한다.
- 만약에 그 호출자가 JVM이라면 프로그램 실행을 종료한다.
throw 명령어를 사용하여 예외 정보를 호출자에게 던진다.
- throw [java.lang.Throwable 타입의 객체 ];
java.lang.Throwable
- 1) java.lang.Error (시스템 오류) // => JVM에서 발생된 오류이다. // => 개발자가 사용하는 클래스가 아니다. // => 이 오류가 발생하면 현재의 시스템 상태를 즉시 백업하고, 실행을 멈춰야 한다. // => JVM에서 오류가 발생한 경우에는 계속 실행해봐야 소용이 없다. // 근본적으로 문제를 해결할 수 없다. // => 오류의 예: // 스택 오버 플로우 오류, VM 관련 오류, AWT 윈도우 관련 오류, 스레드 종료 오류 등 // // 2) java.lang.Exception (애플리케이션 오류) // => 애플리케이션에서 발생시킨 오류이다. // => 개발자가 사용하는 클래스이다. // => 적절한 조치를 취한 후 계속 시스템을 실행하게 만들 수 있다. // => 오류의 예: // 배열의 인덱스가 무효한 오류, I/O 오류, SQL 오류, Parse 오류, 데이터 포맷 오류 등
오류를 던질 때
- 오류를 던진다면 반드시 메서드 선언부에 어떤 오류를 던지는지 선언해야 한다. // => 메서드 호출자에게 알려주는 것이다. // static void m1() throws Throwable { // throw new Throwable(); // OK! // // 예외를 던질 때 Throwable 클래스를 직접 사용하지 말라! // // 그 하위 클래스를 사용하라. // // 특히 애플리케이션 오류를 의미하는 Exception 클래스를 사용하라. // } // // // 여러 개의 오류를 던지는 경우 메서드 선언부에 그대로 나열하라. // static void m2() throws FileNotFoundException, RuntimeException { // int a = 100; // if (a < 0) // throw new FileNotFoundException(); // OK! // else // throw new RuntimeException(); // OK! // } // // public static void main(String[] args) {} }
Error 계열의 예외는 던져서는 안된다.
- Error 계열의 예외는 JVM 관련 오류일 때 사용하는 클래스이다.
- 혹 던지게 되면 메서드 선언부에 던지는 예외 표시를 해도되고 안해도 된다.
Exception 계열의 예외를 던질 경우
/ Exception 계열의 예외를 던질 경우, // // 반드시 메서드 선언부에 어떤 예외를 던지는지 지정해야 한다. // // => 보통 개발자가 애플리케이션을 작성하면서 // // 예외를 던질 경우 이 클래스(및 하위 클래스)를 사용한다. // static void m1() throws Exception { // throw new Exception(); // // OK! 보통 개발자가 사용하는 예외 클래스이다. // } // // // Exception 예외를 던질 경우 반드시 메서드 선언부에 표시해야 한다. // static void m2() { // 컴파일 오류! // throw new Exception(); // } // // // 메서드의 throws 에 선언할 수 있는 클래스는 Throwable 타입만 가능한다. // static void m3() throws String { // throw new String(); // 컴파일 오류! // // throw 로 던질 수 있는 객체는 오직 java.lang.Throwable 타입만 가능하다.
RuntimeException
// // RuntimeException은 클래스는 Exception의 서브클래스이다. // static void m() throws RuntimeException { // throw new RuntimeException(); // OK! // } // // // Exception의 서브 클래스임에도 불구하고 // // RuntimeException 객체를 던질 경우, // // 메서드 선언부에 예외를 던진다고 표시하지 않아도 된다. // // 왜? // // => 스텔스 모드(비유!) 를 지원하기 위해 만든 예외이다. // static void m2() { // throw new RuntimeException(); // }
메서드에서 발생되는 예외는 메서드 선언부에 모두 나열해야 한다.
// static void m(int i) throws Exception, RuntimeException, SQLException, IOException { // if (i == 0) // throw new Exception(); // else if (i == 1) // throw new RuntimeException(); // else if (i == 2) // throw new SQLException(); // else // throw new IOException(); // } // // public static void main(String[] args) {} //}
- 메서드에서 발생하는 예외의 공통 수퍼 클래스를 지정하여 여러개를 나열하지 않을 수 있다.
- 그러나 호출자에게 어떤 오류가 발생하는지 정확하게 알려주는 것이 유지보수에 도움이 되므로 가능한 그 메서드에서 발생하는 예외는 모두 나열하라.
- RuntimeException 계열은 생략 가능하다.
던지는 예외를 예외처리 안 했을 때
- 예외를 던질 수 있다고 선언된 메서드를 호출할 때 그 예외 상황에 대한 처리를 하지 않으면 컴파일 오류가 발생한다.
예외 처리 방법
- 1) 예외를 처리하고 싶지 않다면 상위 호출자에게 책임을 떠넘길 수 있다.
- 만약 상위 호출자가 main()일 경우, main() 호출자는 JVM이고, JVM은 예외를 받는 순간 즉시 실행을 멈춘다.
- 그래서 main()의 호출자에게 책임을 떠넘기는 것은 바람직하지 않다.
- main() 은 예외 처리의 마지막 보루이다.
- main()에서 예외 처리를 하지 않는다면 프로그램은 멈춘다.
- 만약 상위 호출자가 main()일 경우, main() 호출자는 JVM이고, JVM은 예외를 받는 순간 즉시 실행을 멈춘다.
- 2) try ~ catch를 사용하여 코드 실행 중에 발생된 예외를 중간에 가로챈다.
- try 블록에는 예외가 발생할 수 있는 코드를 둔다.
- 그 예외 객체를 파라미터로 받을 수 있는 catch 문을 찾아서 실행한다.
- catch 블록에서는 그 예외를 받아서 처리한다.
- 3) try ~ catch를 사용할 때
- 여러개의 예외를 받을 때 수퍼 클래스 변수를 먼저 받지 않는 것이 좋다.
- 수퍼 클래스 변수를 먼저 받으면 수퍼 클래스의 서브 클래스 객체도 전부 다 받게 된다.
- 그러므로 서브 클래스는 변수를 받지 못하고 예외 객체를 정확하게 받을 수 없게 된다.
- 이를 막기 위해 서브 클래스 예외를 먼저 받는다.
- 3-1) try ~ catch를 사용할 때
- 수퍼 클래스인 Exception을 변수로 사용하면 이 블록에서 예외를 모두 처리할 수 있다.
- 수퍼 클래스 : Exception
- 서브 클래스 : RuntimeException, SQLException, IOException
- 3-2) try ~ catch를 사용할 때
- or 연산자를 사용하여 여러 개의 예외를 묶어 받을 수 있다.
- Exception 변수는 따로 빼야한다.
Throwable 변수로 예외를 받지 말라!
// 가능한 Error 계열의 시스템 예외를 받지 말라! // // 혹 받더라도 현재 프로그램을 종료하기 전에 // // 필수적으로 수행해야 하는 마무리 작업만 수행하도록 하라. // // 왜? // // 시스템 예외는 당장 프로그램을 정상적으로 실행할 수 없는 상태일 때 발생한다. // // 정상적인 복구가 안되는 예외이다. // // 따라서 이 예외를 처리하려 해서는 안된다. // // 시스템을 멈출 수 밖에 없다.
// catch 문을 작성할 때 // // 이처럼 무심코 Throwable 변수로 선언하면 // // 시스템 오류인 Error 까지 받기 때문에 // // JVM에서 발생된 오류에 대해서도 예외 처리를 하는 문제가 발생한다. // // 시스템 오류는 애플리케이션에서 처리할 수 없다. // // 따라서 실무에서는 예외를 받을 때 // // Throwable 변수를 사용하지 않는다.
// 이렇게 Exception 변수를 사용하면 // // 애플리케이션 예외를 처리하고, // // 시스템 예외는 main() 호출자에게 위임하게 된다. // // 즉 JVM에게 전달한다. // // 이렇게 Exception 계열의 애플리케이션 예외만 처리하라.
finally 블록
정상적으로 실행하든, 아니면 예외가 발생하여 catch 블록을 실행하든 // // finally 블록은 무조건 실행한다. // // 즉 try ~ catch ~ 블록을 나가기 전에 반드시 실행한다. // // 그래서 이 블록에는 // // try 에서 사용한 자원을 해제시키는 코드를 주로 둔다. // // => 자원 해제 코드를 둔다. // // => 자원? 파일, DB 커넥션, 소켓 커넥션, 대량의 메모리 등
public static void main(String[] args) throws Exception { // try { // m(1); // // m()에서 발생된 예외는 // // try 블록에서 받지 않는다. // // 따라서 main() 호출자에게 위임한다. // // => 물론 main() 메서드 선언부에 위임할 예외의 종류를 표시해야 한다. // // // } finally { // // try 블록을 나가기 전에 무조건 실행해야 할 작업이 있다면 // // catch 블록이 없어도 finally 블록만 사용할 수 있다. // System.out.println(“마무리 작업 실행!”);
### finally 블록과 자원해제 // 스캐너 객체를 사용하여 키보드 입력을 읽어들인다. // // => 예외가 발생한다면? // System.out.print(“입력> “); // int value = keyScan.nextInt(); // System.out.println(value * value); // // // 프로그램을 즉시 종료한다면, // // 스캐너를 다 사용하고 난 다음에 굳이 스캐너에 연결된 키보드와 연결을 끊을 필요는 없다. // // JVM이 종료되면 OS는 JVM이 사용한 모든 자원을 자동으로 회수하기 때문에 // // 굳이 close()를 호출하지 않아도 된다. // // // // 그러나 365일 24시간 내내 실행되는 시스템이라면, // // 키보드 입력을 사용하지 않는 동안에는 // // 다른 프로그램에서 사용할 수 있도록 // // 스캐너와 연결된 키보드를 풀어줘야 한다. // // // // 이것을 “자원해제”라고 부른다. // // // // 보통 자원해제시키는 메서드의 이름이 “close()”이다. // // 당연히 Scanner 클래스에도 자원을 해제시키는 close() 메서드가 있다. // // 그런데 우리는 지금까지 Scanner를 사용하면서 close()를 호출하지 않았다. // // // // 왜? // // 프로그램이 바로 종료되기 때문이다. // // 그러나 우리가 자바로 프로그램을 짤 영역은 서버쪽이다. // // 즉 365일 24시간 내내 실행되는 서버쪽 프로그램을 개발하는 것이기 때문에, // // 항상 자원을 사용한 후 해제시키는 것을 습관적으로 해야 한다. // // // keyScan.close(); // // // 문제는 close()를 호출하기 전에 예외가 발생한다면, // // 제대로 자원을 해제시키지도 못한다는 것이다. // // 이것을 해결하는 방법은 finally 블록을 사용하는 것이다. }
try-whit-resources 문법
자원해제시키는 코드를 매번 작성하기가 귀찮다! // // => try-with-resources 라는 문법을 사용하면 // // 굳이 finally 블록에서 close()를 직접 호출할 필요가 없다. // // 자동으로 처리한다. // // => 단 java.lang.AutoCloseable 구현체에 대해서만 가능하다! // // => 문법 // // try (java.lang.AutoCloseable 구현체) {…}
// // finally 블록에서 B의 close()를 호출하지 않아도, // // 자동으로 호출될 것이다. 실행하여 확인하라!
A 클래스는 AutoCloseable 구현체가 아니기 때문에 여기에 선언할 수 없다. // // A obj = new A(); // 컴파일 오류!
변수 선언은 반드시 괄호 안에 해야한다.