Java 에서 제공하는 예외처리 방법
그러나, 예외 처리를 위해 모든 코드에 try-catch 를 붙이는 것은 비효율적이다.
try/catch 로 예외를 잡고 아무 조치도 하지 않는 것은 다음과 같은 이유가 있는게 아니라면 반드시 피해야 한다.
(= 오류가 있어서 예외가 발생했는데, 그것을 무시하고 진행하는 경우와 비슷)
→ 어떤 기능이 비정상적으로 동작하거나, 메모리나 리소스가 고갈되는 등의 문제를 야기할 수 있다.
→ 따라서 예외 처리 시에는 빈 값을 반환하는 등...의 조치를 통해 상황을 적절하게 복구 or 작업을 중단시키고 관리자에게 이를 전달하자
try {
// 예외 발생할 가능성있는 문장
} catch (Exception1 e1) {
// Exception1 e1 예외 발생할 경우, 이를 처리하기 위한 문장
} catch (Exception2 e2) {
// Exception1 e2 예외 발생할 경우, 이를 처리하기 위한 문장
} catch (ExceptionN eN) {
// Exception1 eN 예외 발생할 경우, 이를 처리하기 위한 문장
}
class EX {
public static void main(String args[]) {
System.out.println(1);
try {
System.out.println(2);
System.out.println(3);
} catch (Exception e) {
System.out.println(4); // 실행 X
}
System.out.println(5);
}
}
/* 출력 결과
1
2
3
5
*/
class EX {
public static void main(String args[]) {
System.out.println(1);
try {
System.out.println(0/0); // 고의로 ArithmeticException 예외를 발생시켰을 때
System.out.println(2); // 실행 X
} catch (ArithmeticException ae) { // 예외가 실행됨
System.out.println(3);
}
System.out.println(4);
}
}
/* 출력 결과
1
3
4
*/
catch 블럭에서 ArithmeticException ae 대신, Exception e 를 사용한다면?
ArithmeticException클래스는 Exception클래스의 자식이다.
따라서, 결과적으로는 동일한 출력 결과가 나오게 된다.ArithmeticException 예외가 발생할 때, ArithmeticException 인스턴스가 생성된다.
따라서 catch (ArithmeticException ae) 은 사실상 if (e instanceOf ae) 의 결과가 true 이므로
ArithmeticException를 대신하여 Exception 사용할 수 있는 것이다.
class EX {
public static void main(String args[]) {
System.out.println(1);
try {
System.out.println(0/0); // 고의로 ArithmeticException 예외를 발생시켰을 때
System.out.println(2); // 실행 X
} catch (ArithmeticException ae) { // 예외가 실행됨
if (ae instatnceof ArithmeticException)
System.out.println("true");
System.out.println("ArithmeticException");
} catch (Exception e) { // ArithmeticException 를 제외한 모든 예외를 처리
System.out.println("Exception");
}
System.out.println(3);
}
}
/* 출력 결과
1
true
ArithmeticException3
3
*/
try {
System.out.println("예외처리1");
return 0;
} catch (Exception e) {
e.printStackTrace();
return 1;
} finally {
System.out.println("예외처리2");
}
try {
// 예외 발생할 가능성있는 문장
} catch (Exception1 e) {
// Exception1 e1 예외 발생할 경우, 이를 처리하기 위한 문장
} catch (Exception2 e2) {
// Exception1 e2 예외 발생할 경우, 이를 처리하기 위한 문장
}
try {
// 예외 발생할 가능성있는 문장
} catch (Exception1 | Exception2 e1) {
// Exception1 e1 예외 발생할 경우, 이를 처리하기 위한 문장
} catch () {
// Exception1 e2 예외 발생할 경우, 이를 처리하기 위한 문장
}
| 기호는 계속해서 붙일 수 있다
참조변수 'e'를 사용한 이유?
주의!
단점
try {
// 예외 발생할 가능성있는 문장
} catch (Exception1 e1) {
// Exception1 e1 예외 발생할 경우, 이를 처리하기 위한 문장
} finally {
// 예외 발생과 무관하게, 항상 수행될 문장
}
// 기존 코드
try {
install();
copy();
delete(); // 중복된 코드
} catch (Exception1 e) {
e.printStackTrace();
delete(); // 중복된 코드
}
// finally를 사용한 코드
try {
install();
copy();
} catch (Exception1 e) {
e.printStackTrace();
} finally {
delete();
}
class Ex {
static void method(boolean b) {
try {
System.out.println(1);
if (b)
throw new ArithmeticException();
System.out.println(2);
} catch (RuntimeException r) {
System.out.println(3);
return;
} catch (Exception e) {
System.out.println(4);
return;
} finally {
System.out.println(5);
}
System.out.println(6);
}
public static void main(String[] args) {
method(true);
method(false);
}
}
/* 출력 결과
1
3
5
1
2
5
6
*/
1 출력
3 출력
5 출력
1 출력
2 출력
5 출력
6 출력
주의!
System.exit() 메서드를 만나게 될 경우,
아무리 finally 이라하더라도 실행되지 않고, 바로 강제 종료된다.
참고: System.exit() 자바 강제 종료
class EX {
public static void main(String args[]) {
try {
Exception e = new Exception("예외 발생!")
throw e;
} catch (Exception e) { // 예외가 실행됨
...
}
System.out.println("프로그램 정상 종료!");
}
}
throw 를 사용하면, 고의로 예외를 발생시킬 수 있다
throw new Exception("예외 발생!");
public void method1() throws Exception {
method2();
}
public void method2() throws Exception {
method3();
}
public void method3() throws Exception {
}
무분별한 throws Exception 은 해당 문구를 보고 여기서 어떤 문제가 발생할 수 있는지와 같은 의미있는 정보를 얻을 수 X
이 메소드를 다른 메소드에서 사용중이라면, throws Exception 이 전파돼버린다.
→ 복구가 불가능한 예외들( SQLException 같이)이라면,
기계적으로 throws를 던지지 않고 가능한 빨리 언체크/런타임 예외로 전환해주자.
컴파일 에러를 피하기위해 무분별한 throws들을 적는 경우가 있다.
(모든 예외를 무조건 던져버리는 throws Exception을 모든 메소드에 기계적으로 넣는 등...)
그 결과로
1. 해당 예외 처리의 역할(실행 중 예외 상황이 발생하는지, 습관적으로 붙여놓은지...)을 확인 X
2. 적절한 처리를 통해 복구될 수 있는 예외 상황도 제대로 처리 X
void method() throws Exception1, Exception2, ... ExceptionN {
// 메서드 내용
}
class EX {
public static void main(String args[]) throws Exception {
method1();
}
static void method1() throws Exception {
method2();
}
static void method2() throws Exception {
throw new Exception();
}
}
class Ex {
public static void main(String[] args) {
try {
method1();
} catch (Exception e) {
System.out.println(5);
}
}
static void method1() {
try {
method2();
System.out.println(1); // 주의! 해당 줄을 출력하진 않음
} catch (ArithmeticException e) {
System.out.println(2);
} finally {
System.out.println(3);
}
System.out.println(4);
}
static void method2() {
throw new NullPointerException();
}
}
/* 출력 결과
3
5
*/
3 출력
하고,5 출력
class EX {
public static void main(String args[]) throws Exception {
try {
...
} catch {
...
}
}
static File createFile(String fileName) throws Exception {
...
}
}
주의!
'3) throw 키워드'와 '4) 메서드에서 예외처리에서의 throws' 를 혼동하지 말자.
class EX {
public static void main(String args[]) {
try {
method1();
} catch (Exception e) {
System.out.println("다시 예외처리!");
}
}
static void method1 throws Exception {
try {
throw new Exception();
} catch (Exception e) {
System.out.println("예외처리!");
throw e; // 다시 예외를 발생시킨다
}
}
}
class Ex{
public static void main(String[] args) {
try {
method1();
System.out.println(6);
} catch (Exception e) {
System.out.println(7);
}
}
static void method1() throws Exception {
try {
method2();
System.out.println(1);
} catch (NullPointerException e) {
System.out.println(2);
throw e;
} catch (Exception e) {
System.out.println(3);
} finally {
System.out.println(4);
}
System.out.println(5);
}
static void method2() {
throw new NullPointerException();
}
}
/* 출력 결과
2
4
7
*/
2 출력
하고, thorw e 로 다시 예외를 발생시키고, finally블럭으로 4 출력
7 출력
try-catch문에서 처리했다가 다시 발생시켜
main메서드에 예외를 전달합니다.
try {
install(); // SpaceException 예외 발생
copy();
} catch (SpaceException e) {
InstallExcepion ie = new InstallExcepion("설치중 에외 발생"); // 예외 생성
ie.initCause(e); // InstallExcepion의 원인 예외를 SpaceException 예외로 지정
throw ie; // InstallExcepion 을 발생시킴
} catch (MemoryException me) {
...
한 예외(원인 예외)가 다른 예외를 발생시킨다.
initCause 는 Exception클래스의 부모 클래스인 Throwable클래스에 정의돼있으므로, 모든 예외에서 사용 가능
발생한 예외를 그냥 처리하지 않고,
원인 예외로 등록 후, 다시 예외를 발생시키는 원인은 무엇일까?
여러가지 예외가 있을 때, 이들을 하나의 큰 예외로 묶기 위해서이다.
이 경우, 묶인 예외 간에는 상속 관계가 아니어도 된다.
체크 예외
static void install() thorows SpaceException, MemoryException { // MemoryException 은 Exception클래스의 자식이므로, 반드시 예외처리가 필요
if (!enoughSpace()) {
throw new SpaceException("공간 부족!");
}
if (!enoughMemory()) {
throw new MemoryException("메모리 부족!");
}
}
체크 예외로 예외처리를 한 것은 프로그래밍 경험이 적은 사람에게도 유용한 방법이기 때문이었다.
그러나 현재는 체크 예외로도 처리할 수 없는 예외들이 생겨났다.
언체크 예외
static void install() thorows SpaceException { // MemoryException 가 언체크 예외가 됐으므로, 선언부에는 따로 선언해주지 않아도 된다
if (!enoughSpace()) {
throw new SpaceException("공간 부족!");
}
if (!enoughMemory()) {
throw new RuntimeException(new MemoryException("메모리 부족!")); // RuntimeException 이 MemoryException 을 감싼다 (언체크 예외가 됨)
}
}
언체크 에외는 선택적인 예외처리가 가능하다.
따라서, 예외처리를 강요받지 않게 된다.
class EX {
public static void main(String args[]) {
System.out.println(1);
try {
System.out.println(0/0);
System.out.println(2);
} catch (ArithmeticException ae) {
ae.printStackTrack(); // 참조변수 ae를 통해, 생성된 ArithmeticException 인스턴스에 접근
System.out.println(ae.getMessage()); // 참조변수 ae를 통해, 생성된 ArithmeticException 인스턴스에 접근
}
System.out.println(4);
}
}
/* 출력 결과
1
java.lang.ArithmeticException: / by zero // printStackTrack() 의 결과
at EX.main(EX.java:6)
/ by zero // getMessage() 의 결과
*/
printStackTrack()
getMessage()
출력 결과 설명
/* 출력 결과
1
java.lang.ArithmeticException: / by zero
at EX.method2(EX.java:12)
at EX.method1(EX.java:8)
at EX.main(EX.java:4)
/ by zero
*/