본문 바로가기
Java/Java Basic

[Java Basic] 23. 예외처리(Exception Handling)

by Rosmary 2022. 8. 3.
728x90
반응형

 

 

 

Java 연습을 위해 코드를 작성하다보면 여러 에러를 맞닥드리게 된다. 작성한 코드가 에러를 가지는 경우, Eclipse의 콘솔창에는 아래와 같은 형태의 문구가 출력된다.

 

 

 

에러가 발생하면서 나타나는 가장 큰 문제는 작성한 프로그램이 비정상 종료를 한다는 것이다. 다른 말로 하자면, 개발자들이 의도한대로 프로그램이 끝까지 동작하지 못하고 중단된다는 것이다.

 

예를 들어보자. 사용자가 금액을 입력하면, 해당 금액만큼 인출하는 프로그램이 있다고 해보자. 

 

 

 

Java로 연습을 많이 진행해보신 분들이라면, 정수 외의 값이 입력되는 경우 에러가 발생한다는 것을 금방 알아차릴것이다. 

 

 

 

위의 예시의 경우, 필자가 의도한 프로그램의 동작이 " $$$ 원이 인출되었습니다:" 에서 종료되는 것임에도 불구하고, 에러로 인해 12번 째 줄에서 중단된 것이 확인된다. 만약 사용자에 의해 정수가 아닌 값이 입력된 경우, 사용자에게 적절하게 경고를 주고 프로그램을 정상 종료할 수 있는 방법은 없을까? 

 

모든 프로그램은 잠재적인 에러를 내포한다. 따라서 프로그래밍의 종류에 상관없이 심각하지 않은 에러를 예외적으로 처리할 수 있는 기능들을 가지고 있다. 이를 "예외 처리(Exception Handling)"라고 한다. 

 

본격적으로 Java의 예외 처리에 대해 알아보자.

 

 

 

1. 에러의 종류

 

프로그램이 실행 중 오작동하거나 비정상 종료되는 것을 오류 또는 에러라고 한다. Java에서는 발생하는 에러를 아래와 같이 세 가지 종류로 구분한다.

 

-  컴파일 에러(Compile Error):  Java 문법에 일치하지 않는 코드로 인해 컴파일 시 발생하는 에러

-  런타임 에러(Runtime Error):  프로그램은 정상적으로 컴파일되지만, 실행 중 발생하는 에러

-  논리적 에러(Logical Error):    의도와 다르게 동작하여 상이한 결과를 나타내는 에러

 

컴파일 에러는 Java를 처음 시작하는 분들이 가장 많이 보았을 에러로 문법에서 벗어난 코드로 인해 발생하는 에러다.콘솔에 표시되는 에러 상단에 "java.lang.Error"라는 문구가 표시된다.

 

 

 

런타임 에러는 컴파일에는 문제가 없으나, 프로그램 실행 중 발생하는 에러다. 런타임 에러는 배열의 크기를 벗어나는 index 값을 출력한다던가, 서두의 예시와 같이 정수를 입력받아야하는데 다른 자료형 값을 입력받는 등의 행위를 예시로 들 수 있다.

 

 

 

마지막으로 논리적 에러는 프로그램 동작에는 문제가 없으나 결과값이 의도한대로 나타나지 않는 에러다. 교통카드에 돈이 10원 남아있음에도 결제가 되면서 잔고가 마이너스로 저장되는 상태를 예시로 들 수 있는데, 프로그램의 중단을 초래하지는 않지만 의도한 결과가 나타나지 않는 에러라 보면 된다.

 

위의 세 가지 에러 중 컴파일 에러와 논리적 에러는 개발자들이 직접 코드를 수정해야하는 부분이다. 하지만 런타임 에러와 같은 경우는 개발자가 아무리 에러 발생상황에 대한 경우의 수와 예방 방법을 무수히 많이 세우더라도 에러가 반드시 발생할 수 있는 부분이 있기 마련이다. 앞서 예를 들었던 입력값을 정수로만 입력받는 부분 등이 이 예시에 속한다. 

 

예외처리를 하다보면 에러와 예외라는 단어를 자주 접하게 되는데, 둘 다 프로그램의 중단과 비정상 종료를 유발하는 에러에 속하나 단 한가지 차이점이 있다.

 

-  에러:  대처할 수 없는 심각한 에러   ex) 메모리부족(OutOfMemory) 등

-  예외:  대처할 수 있는 가벼운 에러   ex) 배열길이초과(ArrayIndexOutOfBounds), 입력값자료형미일치(InputMismatch)

 

즉, 예외처리는 프로그램 실행 과정에서 발생할 수 있는 대처 가능한 에러로 인해 프로그램이 중단되지 않도록 조치하는 절차라 보면 된다. 

 

 

 

 

2. 예외 클래스의 구조 

 

 

늘상 언급하지만 Java는 객체지향언어다. 일부 기본형 타입을 제외하면 모든 객체들이 클래스로 정의되어 있고, 클래스로부터 인스턴스 생성이 가능하다. Java에서도 에러로 발생하는 예외들이 클래스로 정의되어 있다. Java의 예외 클래스만 구조를 그려보면 아래와 같이 표현할 수 있다.

 

 

우측 그림은 Java Documentation의 RuntimeException 화면 상단의 내용이다.

 

모든 예외는 Throwable 클래스 아래에 위치한 Exception과 그 하위 클래스인 RuntimeException에 존재한다. 이 구조를 보면 알겠지만 Java의 예외도 그 특성에 따라 분류가 나뉜다.

 

-  일반 예외(Exception) 및 자손 예외

-  런타임 예외(Runtime Exception) 및 자손 예외

 

각 예외가 어느 분류에 속하는지는 Java Documentation의 검색을 통해 알 수 있다.

 

 

앞의 두 에외 클래스는 런타임 에외로, 마지막 예외 클래스는 일반 예외로 분류된다. 

 

 

 

3. 예외 처리 : try - catch 문

 

위의 입력값 오류 발생 코드를 통해 예외 처리 방법을 알아보자. Java에서 예외 처리에 사용하는 구문은 try-catch 구문이다(참고로 프로그램마다 예외처리 구문이 다르다. Python은 try - except 문을 사용한다). try-catch 문은 아래와 같이 사용한다.

 

try 

{

     예외 발생 가능성이 있는 코드;

     예외 미발생 시 진행할 코드;

}

catch (예외클래스자료형 예외클래스자료형참조변수명)

{

     예외 발생 시 진행할 코드 작성;

}

 

 

try 문에 도달하여 예외 발생 가능성이 있는 코드를 실행할 때, 예외가 발생하지 않으면 예외 미발생 시 진행할 코드을 실행하고, 그렇지 않다면 catch문으로 넘어간다. catch문의 경우, 인자로 예외 클래스를 자료형으로 삼는 참조변수를 받는데, 만약 발생한 예외가 인자의 참조변수와 동일한 타입이라면 예외 발생 시 진행할 코드 작성가 실행된다.

 

서두에서 에러가 발생한 코드의 내용을 보면 InputMismatch 예외가 input_number = new Scanner(System.in).nextInt(); 에서 발생하는데, 이 예외를 처리할 수 있는 catch 문이 없기 때문에 콘솔에 해당 예외 클래스에 대한 에러가 나타나는 것이다. 이 예외를 처리하기 위해 try-catch문을 적용하면 아래와 같은 형태를 띈다.

 

 

 

Eclipse 콘솔창을 보면 필자가 정수가 아닌 Hello를 입력했음에도 에러로 인해 프로그램이 비정상적으로 종료되지 않음을 확인할 수 있다. 정리하자면 try-catch 문은 try 문 내에서 발생하는 예외를 catch 문 내에서 처리하는 기능을 한다고 보면 된다.

 

만약, InputMismatch가 아닌 ArrayIndexOutOfBounds가 catch 문 인자에 자료형으로 정의되어 있다면 결과가 어떻게 될까? catch () 문은 발생한 예외를 받아 인자의 참조타입과 동일한지의 여부를 true, false로 반환하고 그 결과가 true 인 경우에만 catch 문 내의 코드를 실행한다. 따라서 위의 코드에서 catch() 인자를 변경하게 되면 여지없이 서두에 보았던 에러가 동일하게 발생한다.

 

 

 

 

만약 하나의 코드에서 여러 에러가 발생할 가능성이 있다면 try-catch 구문을 어떻게 작성해야 할까? 사용자가 입력한 핸드폰 뒷 4자리 각 숫자를 배열로 저장하는 코드가 있다고 가정해보자.

 

 

 

 

 

위의 코드는 사용자가 숫자가 아닌 값을 입력하거나, 4자리 이상의 숫자를 입력하는 경우 에러가 발생하게 되는 코드다. 따라서 두 예외 상황에 대한 처리를 진행해주어야 한다. 둘 이상의 예외 처리를 진행하는 방법은 여러가지가 있지만, 보편적으로 아래와 같이 catch() 문을 중복으로 사용한다.

 

 

 

 

 

만약, 예상하지 못한 에러가 나타나는 경우에는 어떻게 해야할까? 어떠한 예외가 발생할 지 모르니 인자를 작성할 수가 없다. 하지만 모든 예외 클래스는 Exception 클래스를 상속하기 때문에 Java의 다형성을 이용하여 쉽게 이 문제를 해결할 수 있다(자세한 내용은 Java의 다형성을 참고하자).

 

 

 

 

만약 특정 예외 클래스와 Exception 클래스를 동시에 catch 문에 사용하고자 한다면, 반드시 마지막 catch ()문의 인자에 Exception 참조타입이 위치해야한다. 에러로 인해 예외가 발생하면 catch() 문을 위에서 순차적으로 훑으면서 내려오는데, Exception을 인자로 가지는 catch() 문이 가장 위에 있는 경우, 하위 catch() 문은 사용되지 않기 때문이다. Exception 뿐만 아니라, 서로 상속관계를 가지는 예외 클래스를 catch() 문으로 동시에 작성하는 경우, 반드시 하위 예외 클래스를 인자로 가지는 catch() 문이 코드 상단에 위치해야 한다. if-else if 조건문과 마찬가지로 조건에 부합하는 catch() 문이 존재하면 하위 catch() 문은 건너뛰기 때문이다.

 

 

 

 

4. Exception 클래스의 printStackTrace(), getMessage() 매서드

 

Java의 Exception 클래스는 printStackTrace()와 getMessage()라는 이름의 매서드를 가진다. 거창한 것은 아니고, 코드에서 에러로 인한 예외 발생 시, 콘솔에 뜨는 글자를 나타내는 매서드라 보면 된다. 예외 클래스의 인스턴스를 통해 호출이 가능한 매서드다. 먼저 printStackTrace()를 보자.

 

 

 

콘솔에는 에러창에서 보던 것과 유사한 문구가 보인다. 하지만 예외처리를 통해 나타난 구문이기 때문에 일반적인 에러와 달리 프로그램이 비정상 종료된 상황은 아니다. 위의 printStackTrace() 매서드는 단순히 예외 발생 시점의 호출 스택(Call Stack)에 위치한 매서드 정보와 에외 메세지를 출력하는 역할을 한다. 위의 예시의 경우, Main() 함수가 호출 스택 내에 위치해 있는 상태에서 예외가 발생했기 때문에 printStackTrace() 문구의 마지막에 at Main.main(Main.java:27) 존재하는 것이다.

 

다음으로 getMessage() 매서드를 보자.

 

 

 

예시에서 getMessage()는 아무런 결과를 도출하지 않는데, 사실 getMessage()는 발생한 예외 클래스의 예외에 대해 설명하는 메세지를 문자열로 반환만 하기 때문이다(System.out.print 문으로 출력할 수 있다). 이 getMessage() 매서드는 개발 과정에서 직접 만든 예외의 발생 원인에 대해 설명하기 위한 용도로 많이 사용한다. getMessage() 매서드의 사용 예시는 바로 다음 포스팅에서 살펴볼 예외 되던지기와 예외 생성 과정에서 자세히 확인해보려 한다.

 

 


 

 

다음 포스팅은 예외 되던지기(throw exception)과 사용자 정의 예외 생성에 대해 알아보려한다.

 

 

Fin.

 

반응형

댓글