[Java] 예외처리 (Exception)_ try-catch / finally / throws

Jeini·2022년 11월 24일
0

☕️  Java

목록 보기
29/59

💡 try-catch


기본적으로 프로그램에서 에러가 나는 경우 프로그램은 중단이 된다.
아무리 잘 만든 프로그램이어도 오류가 단 한번도 없이 돌아가기란 힘들다.
프로그램에서 오류가 난 경우 그에 대한 예외처리를 하기 위해 try-catch문을 사용한다.

예외가 발생될 만한 코드try 를 넣는다
코드를 실행 중 에러가 나면 그 자리에서 중단되고 catch 문으로 이동한다.
오류가 없다면 try 안의 구문을 모두 실행하고 catch 문 다음의 문장들은 수행이 되지 않는다.

✏️ 예시

class ExceptionEx1 {
	public static void divide(int n1, int n2) {
		int result = n1 / n2;
		System.out.println(result);
	}
	public static void main(String[] args) {
		divide(12, 6);
		divide(12, 0); // ArithmeticException 예외발생
        System.out.println("end of main"); // 이까지 안감. 예외가 발생하면 중간에 죽음
	}
}
[결과값]
Exception in thread "main" java.lang.ArithmeticException: / by zero
        at ExceptionEx1.divide(ExceptionEx1.java:3)
        at ExceptionEx1.main(ExceptionEx1.java:8)

ArithmeticException은 정수를 0으로 나눌경우 나오는 예외이다.
즉, 8번줄에 divide(12, 0)을 하고 있기 때문에 예외가 발생한 것이다.

처리하는 방법은 try-catch 를 사용하면 된다.

class ExceptionEx1 {
	public static void divide(int n1, int n2) {
		try {
			int result = n1 / n2; //예외발생
			System.out.println(result); //의존성이 있는 연산아므로 try에 같이 넣어준다.
			System.out.println("end of try");
		} catch(ArithmeticException e) {
			//예외가 발생하여 이 문장이 수행된다.
			System.out.println("0으로 나눌 수 없습니다.");
            System.out.println("end of catch");
		}
	}
	public static void main(String[] args) {
		divide(12, 6);
		divide(12, 0);
		System.out.println("end of main");
	} 
}
[결과값]
2
end of try
0으로 나눌 수 없습니다.
end of catch
end of main

0으로 나누지 못할 때 예외가 나타나므로 try-catch 문을 이용하여 예외를 막아준다.
catch 는 대신할 일을 알려주는 것.

12 / 0 할 때 try 를 뛰어넘고 바로 catch 로 간다.

❗️ 여러 예외 타입의 동시 처리

Java SE 7부터는 | 기호를 사용하여 하나의 catch 블록에서 여러 타입의 예외를 동시에 처리할 수 있다.

try {
    this.db.commit();
    
} catch (IOException | SQLException e) {
    e.printStackTrace();
}

하지만 둘 이상의 예외 타입를 동시에 처리하는 catch 블록에서 매개변수로 전달받은 예외 객체는 묵시적으로 final 제어자를 가지게 된다.

따라서 catch 블록 내에서 해당 매개변수에는 어떠한 값도 대입할 수 없다.

💡finally


프로그램 수행 도중 예외가 발생하면 프로그램이 중지되거나 예외 처리에 의해 catch 구문이 실행된다. 하지만 어떤 예외가 발생하더라도 반드시 실행되어야 하는 부분이 있어야 한다면 어떻게 해야 할까?

다음의 예제를 보도록 하자.

✏️ 예시

public class FallyTest {
    public void shouldBeRun() {
        System.out.println("ok thanks.");
    }

    public static void main(String[] args) {
        int c;
        try {
            c = 4 / 0; //ArithmeticException 예외발생
            shouldBeRun();  // 이 코드는 실행되지 않는다.
        } catch (ArithmeticException e) {
            c = -1;
        }
    }
}

위 예를 보면 shouldBeRun() 메서드는 절대로 실행될 수 없을 것이다. 왜냐하면 4 / 0 에 의해 ArithmeticException이 발생하여 catch 구문으로 넘어가기 때문이다.

shouldBeRun() 메서드는 반드시 실행되어야 하는 메서드라고 가정해 보자. 이런 경우를 처리하기 위해 자바는 finally 구문을 제공한다.

public class FinallyTest {
    public static void shouldBeRun() {
        System.out.println("ok thanks.");
    }

    public static void main(String[] args) {
        int c;
        try {
            c = 4 / 0;
        } catch (ArithmeticException e) {
            c = -1;
        } finally {
            shouldBeRun();  // 예외에 상관없이 무조건 수행된다.
        }
    }
}
[결과값]
ok thanks.

finally 구문은 try 문장 수행 중 예외발생 여부에 상관없이 무조건 실행된다. 따라서 위 코드를 실행하면 shouldBeRun() 메서드가 수행되어 "ok, thanks" 문장이 출력될 것이다.

🧬 예외 처리 메커니즘


❗️ 자바에서 예외 처리는 다음과 같은 순서로 진행된다.

  1. try 블록에 도달한 프로그램의 제어는 try 블록 내의 코드를 실행한다.
    이때 만약 예외가 발생(throw)하지 않고, finally 블록이 존재하면 프로그램의 제어는 바로 finally 블록으로 이동한다.

  2. try 블록에서 예외가 발생하면 catch 핸들러는 다음과 같은 순서로 적절한 catch 블록을 찾게 된다.

    2-1. 스택에서 try 블록과 가장 가까운 catch 블록부터 차례대로 검사한다.

    2-2. 만약 적절한 catch 블록을 찾지 못하면, 바로 다음 바깥쪽 try 블록 다음에 위치한 catch 블록을 차례대로 검사한다.

    2-3. 이러한 과정을 가장 바깥쪽 try 블록까지 계속 검사하게 된다.

    2-4. 그래도 적절한 catch 블록을 찾지 못하면, 예외는 처리되지 못한다.

  3. 만약 적절한 catch 블록을 찾게 되면, throw 문의 피연산자는 예외 객체의 형식 매개변수로 전달된다.

  4. 모든 예외 처리가 끝나면 프로그램의 제어는 finally 블록으로 이동한다.

  5. finally 블록이 모두 처리되면, 프로그램의 제어는 예외 처리문 바로 다음으로 이동한다.

📌 간단 정리

  • 만약 ①번 try 블록에서 예외가 발생하지 않고, 바깥쪽 try 블록에서도 예외가 발생하지 않으면, ⑥번 finally 블록이 바로 실행될 것이다.

  • 하지만 ①번 try 블록에서 예외가 발생하면, ②번과 ③번 catch 블록에서 해당 예외를 처리할 수 있는지 검사하게 된다.

  • 만약 적절한 catch 블록을 찾지 못하면, 바깥쪽 try 블록의 ④번과 ⑤번 catch 블록도 차례대로 검사하게 된다.

  • 이때 해당 예외를 처리할 수 있는 catch 블록을 찾게 되면, 해당 catch 블록을 실행한 후 ⑥번 finally 블록을 실행한다.

  • 하지만 모든 catch 블록이 해당 예외를 처리할 수 없으면, 예외는 처리되지 못한 채 해당 프로그램은 강제 종료될 것이다.

💡 throws (예외 던지기)


예외를 던진다는 말이 있다. 그 의미는 예외를 여기서 처리하지 않을테니 나를 불러다가 쓰는 녀석에게 에러 처리를 전가하겠다는 의미이며 ("예외를 뒤로 미루기"라고도 한다.) 코드를 짜는 사람이 이 선언부를 보고 어떤 예외가 발생할 수 있는지도 알게 해준다.

✏️ 호출된 메서드에서 처리

먼저 호출된 메서드에서 발생한 예외를 호출된 메서드에서 처리하는 예제를 보자.

public class ExceptionEx {
    public static void handlingException() {
        try {
            throw new Exception();
        } catch (Exception e) {
            System.out.println("호출된 메서드에서 예외가 처리됨!");
        }
    }
    public static void main(String[] args) {
        try {
            handlingException();
        } catch (Exception e) {
            System.out.println("main() 메서드에서 예외가 처리됨!");
        }
    }
}
[결과값]
호출된 메서드에서 예외가 처리됨!

여기서 호출된 메서드의 try-catch 문을 생략하면 컴파일 오류가 발생한다.

그러면 이 메소드를 호출한 main() 메서드는 호출된 메서드에서 예외가 발생한 사실을 알 수 없다.

✏️ throws 호출한 메서드로 넘겨서 처리

다음 예제는 throws 키워드를 사용하여 호출된 메서드에서 발생한 예외를 호출한 메서드로 넘기는 예제이다.

public class ExceptionEx {
    static void handlingException() throws Exception {
    	throw new Exception(); 
    }
    
    public static void main(String[] args) {
        try {
            handlingException();
        } catch (Exception e) {
            System.out.println("main() 메소드에서 예외가 처리됨!");
        }
    }
}
[결과값]
main() 메소드에서 예외가 처리됨!

이렇게 함으로써 호출된 메서드에는 try-catch 문을 생략할 수 있다.

그리고 호출된 메서드에서 발생한 예외를 해당 메소드를 호출한 main() 메서드에서 처리할 수 있게 된다.

💡 사용자 정의 예외 클래스


자바에서는 Exception 클래스를 상속받아 자신만의 새로운 예외 클래스를 정의하여 사용할 수 있다.

사용자 정의 예외 클래스에는 생성자뿐만 아니라 필드 및 메서드도 원하는 만큼 추가할 수 있다.

class MyException extends RuntimeException {
    
    MyException(String errMsg) {
        super(errMsg);
    }
}

요즘에는 위와 같이 Exception 클래스가 아닌 예외 처리를 강제하지 않는 RuntimeException 클래스를 상속받아 작성하는 경우가 많다.


References
: http://www.tcpschool.com/java/java_exception_throw
: https://wikidocs.net/229

profile
Fill in my own colorful colors🎨

0개의 댓글