오류 Error & 예외 Exception
프로그래밍을 하다가 오류가 발생했을 때, 회복이 가능한가를 기준으로 오류와 예외로 나눈다.
- 오류 Error
- 일반적으로 회복이 불가능한 문제
- 환경적인 이유로 발생
- 발생하는 경우 회복이 불가능하며 어떤 에러로 종료됐는지 확인할 수 있다.
- 예외 Exception
- 회복이 가능한 문제
- 발생할 수 있는 문제에 대해 대응하는 예외 처리가 가능
예외 종류
- 확인된 예외 Checked Exception
- 컴파일 시점에 확인하는 예외로 특정 문제를 인지하고, 정의해두어서 확인이 가능하다.
- checked exception은 예외 처리를 하지 않으면 compile error가 발생한다.
- compile error - 프로그래밍 언어 규칙을 지키지 않았을 때 발생하는 에러, 문법에 맞게 작성
- 미확인된 예외 Unchecked Exception
- 런타임 시점에 확인되는 개발자 부주의로 발생하는 예외 (문법 오류x)
- 예외 처리가 반드시 필요하지 않다.
예외 클래스와 자주 발생하는 예외
예외는 Object 아래의 Throwable을 상속받고 있다. Exceiption 종류는 여러가지가 있고, 그 중에 RuntimeExcepiton은 상황에 맞게 대응하는 미확인 예외(unchecked exception)으로 꼭 예외처리를 하지 않아도 된다. RuntimeException을 제외한 나머지 예외는 확인예외(checked exception)로 예외 처리를 필수로 해야 한다.
자주 발생하는 예외
예외 클래스 | 예외 발생 이유 |
ArithmeticException | 어떤 수를 0으로 나눌 때 발생 |
NullPointerException | Null 값을 참조할 때 발생 |
ArrayIndexOutOfBoundsException | 배열 범위를 벗어나서 접근하는 경우 발생 |
ClassNotFoundException | 클래스에 접근할 수 없을 때 발생 |
FileNotFoundException | 지정된 파일을 찾을 수 없을 때 발생 |
EOFException | 입력 중 예기치 않게 파일의 끝에 도달하는 경우 발생 |
예외 종류는 이 보다 더 많으며, 각 예외마다 발생 조건을 가지고 있다. 런타임 에러가 발생하게 되면, 그 발생하는 이유와 함께 예외가 출력된다. 예외 처리를 하지 못하더라도 JVM의 예외처리기(Uncaught Exception Handler)에서 예외 원인을 출력해준다.
public static void main(String[] args) {
int a = 3;
int b = 0;
System.out.println(a/b);
}
문법 오류가 아니니 컴파일 에러는 발생하지 않는다. 어떤 수를 0으로 나누는 연산은 할 수 없다. 수학적예외가 발생했다는 문구와 함께 어떤 패키지의 어떤 클래스, 몇번째 줄에서 발생했는지 그 라인도 알려준다.
예외 처리 Exception Handling
이와 같은 예외 발생을 대비하기 위해 코드를 작성하는 것을 예외 처리 라고 한다. 예외 처리를 한다고 예외 상황이 발생하지 않는 것이 아니다. 그저 상황을 대비만 하는 것으로, 프로그램이 비정상적으로 종료되는 것을 막기 위해 사용한다.
예외 처리를 통해 에러를 잡아내어 에러가 발생하지 않는 조건으로 복구하거나, 해당 기능을 실행하지 않고 회피하기도 한다. 예외 처리는 그저 예외가 발생하는 경우 대비책으로, 정상적인 실행 상태를 유지하려는 목적을 가지고 있다.
예외 처리 하는 방법
1. try ~ catch
try{
// 실행할 구문
} catch (Exception e){
// 예외 처리할 구문
}
예외 처리를 위한 기본 구조는 try-catch
로 되어 있다.try
안에 있는 내용은 일단 실행을 할 부분, 예외가 발생할 수도 있는 부분이 들어간다.
그리고 try에서 예외가 발생하면 catch
에서 그 예외를 잡아낸다.
catch의 인자로 예외클래스 타입의 변수가 선언되어야 한다.
위에서는 Exception타입의 e변수 → e를 이용해 예외 관련 메서드를 실행한다.
2. 예외를 여러개 처리하는 다중 catch
try{
...
} catch(ArrayIndexOutOfBoundsException ae){
...
} catch(NullPointerException ne){
...
} catch(Exception e){
...
}
catch는 여러개가 올 수 있다. try를 실행하다가 예외가 발생하면 그 예외에 따라 catch 인자에 배정될 수 있는지 조사한다.
다중 catch를 사용할 때 작성한 예외를 순서대로 비교한다.
순서대로 비교한다는 것에 주목해야 한다. 만약 위 코드에서 모든 예외를 잡아낼 수 있는 Exception이 제일 뒤가 아닌 첫번째 catch에 있다고 가정하면, 다른 예외는 잡아내지 못하고 모두 Exception에서 잡아낼 것이다.
public class Test {
public static void main(String[] args) {
try{
Integer[] arr = new Integer[3];
arr[1]= null;
System.out.println(arr[1].intValue());
} catch(ArrayIndexOutOfBoundsException ae){
ae.printStackTrace();
} catch(NullPointerException ne){
ne.printStackTrace();
} catch(Exception e){
e.printStackTrace();
}
}
}
배열 arr의 1번 인덱스에 null
값이 들어있다.
이 arr[1]의 요소를 참조하려고 할 때, NullPointerException
이 발생한다.
그리고 그 메세지를 보면 "arr[1]" is null
이라고, 어느 부분이 null인지 정확히 알려주고 있다.
같은 코드에서 System.out.println(arr[1].intValue())
를 System.out.println(arr[4])
로 바꾸면 아래와 같이 나온다.
11번째 라인에서 ArrayIndexOutOfBoundsException
이 발생했다.
배열 길이는 3인데 인덱스 4에 접근해서 발생한다고 메시지가 나온다.
다중 catch를 사용하면, 각 에러에 맞는 예외를 찾아간다.
만약 첫번째 catch문에서 Exception으로 잡으면 어떻게 될까?
첫번째였던 ArrayIndex~Exception
과 Exception
의 자리를 바꾸면 컴파일 에러가 발생한다.
다중 catch에서는 입력한 예외를 먼저 조사하면서 맞는 예외를 찾아간다.
Exception은 예외클래스들이 상속하는 클래스로, 모든 예외를 다 잡을 수 있다.
이미 첫번째 catch가 모든 예외를 잡는 Exception이 있으니 그 다음 catch는 조사할 필요가 없다.
다중 catch를 사용할 때 하위 클래스부터 작성하고 점점 상위 클래스로 올라가야 한다.
어차피 Exception이 모든 예외를 잡을 수 있으니 클래스를 나누지 말고 Exception을 써도 되지 않을까? 라는 의견이 등장할 수 있다. 그냥 Exception을 사용해도 예외를 잡을 수 있지만 좋은 방법은 아니다. 어떤 예외를 잡아낼지 자세하게 작성할수록 코드 가독성이 좋아진다.
3. 예외 발생과 상관 없는 finally
try{
...
} catch (Exception e){
...
} finally{
// 꼭 실행할 부분
}
예외가 발생하는지 안 하는지 상관하지 않고, 반드시 실행해야 하는 부분은 finally
로 작성한다.
try의 코드를 실행하던 도중 예외가 발생하면 catch에서 잡아낸다. catch에서 예외 처리를 하면, finally로 넘어간다.
try에서 예외가 발생하지 않으면, catch는 동작하지 않는다. 바로 finally로 넘어간다.
예외가 발생해도 finally는 실행되고, 예외가 발생하지 않아도 finally는 실행된다.
4. 예외를 발생시키는 throw
try{
...
Exception e = new Exception();
throw e;
// throw new Exception();
}
catch(Exception e){
...
}
개발자의 의도대로 작동하는지 확인하는 검증 과정을 통과하지 못할 때 예외를 고의로 발생시킨다.
개발자가 직접 예외를 발생시킬 때 예외 객체를 생성해 throw
로 던진다.
catch에서는 throw로 던진 예외를 받아서 처리한다.
여기서 throw로 던진 예외와 catch로 잡는 예외는 같아야 한다.
public class Test {
public static void main(String[] args) {
try{
Integer[] arr = new Integer[3];
throw new ArrayIndexOutOfBoundsException();
} catch (NumberFormatException ne){
System.out.println("catch = NumberFormatException");
ne.printStackTrace();
} catch(ArithmeticException ae){
System.out.println("catch = ArithmeticException");
ae.printStackTrace();
} catch (ArrayIndexOutOfBoundsException ae){
System.out.println("catch = ArrayIndexOutOfBoundsException");
ae.printStackTrace();
}
}
}
코드 자체에 아무 문제는 없지만 throw를 이용해 임의로 예외를 던졌다.
그리고 catch는 NumberFormatException
과 ArithmeticException
그리고 throw로 던진 ArrayIndexOutOfBoundsException
을 잡고 있다. 실제로 에러는 일어나지 않았지만, 발생시키고 있는 예외는 Array~Exception
이 코드를 실행시키면 Array~Exception으로 catch를 했다는 내용이 뜬다.
종료 코드 0(으)로 완료된 프로세스 ⇒ 에러 없이 프로그램이 성공적으로 실행되었다가 종료됨
만약 throw로 던지고 있는 예외가 catch에 없으면 아래와 같이 뜬다.
Array~Exception이 발생했다는 예외는 그대로 뜬다. → 직접 발생시켰기 때문에
하지만 그 예외를 잡지 못해 제대로 실행되지 않아, 종료 코드 1(으)로 완료된 프로세스로 나온다.
개발자가 직접 예외를 던질 때는 throw를 이용해 예외 객체를 넘겨주면 된다.
그리고 그 예외를 잡을 때는 해당 예외에 알맞는 catch가 있어야 한다.
같은 예외를 catch하는 것은 일부러 throw를 사용하지 않아도 필요한 내용이다.
예외가 발생을 한다는 것은 동일하다. 그저 직접 발생하냐, 실행 도중에 발생하냐의 차이다.
발생하는 예외를 잡을 수 있어야 예외 처리가 제대로 이루어진다.
5. 호출하는 곳으로 예외 처리를 떠넘기기 throws
각 예외 처리는 메서드 안에서 실행된다. 그리고 그 메서드 안에서 예외 처리를 하지 않고, 메서드를 호출한 곳으로 예외 처리를 떠넘기고 싶을 경우에는 throws
를 사용한다.
호출하는 클래스나 메서드의 이름 옆에 throws Exception
을 작성한다.
public class CalculatorApp {
...
public static void main(String[] args){
...
try{
ArithmeticCalculator calculator = new ArithmeticCalculator(input);
...
} catch(Exception e){
System.out.println(e.getMessage());
}
}
}
public ArithmeticCalculator(String input) throws WrongInputException {
inputInfo = new InputInfo(input);
}
public InputInfo(String input) throws WrongInputException {...}
CalculatorApp
에서 ArithmeticCalculator
객체를 인스턴스 하면서 생성자를 호출한다.ArithmeticCalculator
생성자 안에는 InputInfo
객체를 인스턴스하고 있다.
InputInfo 생성자 안에서 WrongInputException
예외가 발생할 수 있다.
이 예외를 InputInfo를 호출한 곳으로 떠넘기기로 한다. → throws
InputInfo를 호출한 ArithmeticCalculator에서도 예외 처리를 하지 않는다.
자신을 호출 시킨 곳으로 WrongInputException을 떠넘긴다. → throws
ArithmeticCalculator를 호출한 CalculatorApp으로 다시 올라갔다.
결국 CalculatorApp
에서 catch
를 통해 예외를 잡아낸다.
이렇게 throws를 사용하면 예외가 발생한 곳이 아닌 자신을 호출시킨 곳으로 예외 처리를 떠넘긴다.
6. 예외 되던지기 exception-re-throwing
예외 되던지기는 예외 처리를 두번 이상 하는 것이다.
발생할 수 있는 예외가 여러개인 경우에 사용한다.
예외가 발생한 내부에서 예외 처리를 하고, 자기를 호출한 곳에서 예외 처리를 한다.
내부에서는 catch로 예외 처리를 하고 throw로 다시 던진다.
호출한 곳에서 throws를 선언하고, catch로 다시 잡아낸다.
7. 연결된 예외 chained exception
특정 예외에서 다른 예외가 발생하는 경우 사용한다.
예외가 서로 연결되어 있어, 예외가 다른 예외를 유발할 수 있는 경우이다.
연결된 예외를 사용하면, checked 예외를 unchecked 예외로 변경할 수 있다. 예외 처리를 하지 않고 넘길 수 있는 것이다.
unchecked 예외로 변경할 때는 Exception 중 유일한 unchecked 예외인 RuntimeException으로 감싼다.
public class Test {
public static void main(String[] args) throws TestException {
try{
throw new OurBadException();
} catch (OurBadException e) {
System.out.println(e.getMessage());
TestException te = new TestException();
te.initCause(e);
throw te;
}
}
}
예외가 발생하면 그 예외를 다른 예외로 wrapping
해서 사용한다.
위 코드에서 throw로 OurBadException
을 던져주었다.
그걸 catch에서 받아 에러 메세지를 출력했고, TestException
객체를 인스턴스하고 initCause
로 처음 예외를 전달했다.
initCause()
는 Throwable
의 메서드로 모든 Exception클래스에서 사용할 수 있다.
그리고 throw를 통해 TestException 객체인 te를 던졌다.
이 te를 잡는 곳은 호출한 메서드 main()에서 throws로 잡고 있다.
실행시키면 그 결과는 어떻게 나올까?
OurBadException 객체 e를 통해서 getMessage()
를 출력했다. → OurBadException 발생
그리고 TestException을 통해서 OurBadException을 initCause()로 감싸고 TestException 예외를 던졌다.
TestException 예외가 발생한 것이 보인다. 그리고 그 원인 Caused으로 OurBadException이 있다.
A 예외가 B 예외를 발생시키면, A는 B의 원인 예외 cause exception 이라고 부른다.
OurBadException은 TestException의 원인 예외인 것이다.
initCause()를 하지 않고도 예외를 연결할 수 있다.
public class Test {
public static void main(String[] args) {
try{
throw new OurBadException();
} catch (OurBadException e) {
throw new RuntimeException(e);
}
}
}
연결된 예외를 사용하는 이유인 RuntimeException
을 이용하면 예외를 바로 넣을 수 있다.
reference
인파. 「자바 예외 처리(try catch) 문법 & 응용 정리」. Inpa Dev.
https://inpa.tistory.com/entry/JAVA-%E2%98%95-%EC%98%88%EC%99%B8-%EC%B2%98%EB%A6%ACException-%EB%AC%B8%EB%B2%95-%EC%9D%91%EC%9A%A9-%EC%A0%95%EB%A6%AC
위키독스. 「07장 자바 날개 달기 - 04 예외 처리」. 점프 투 자바. https://wikidocs.net/229
Catsbi. 「예외처리(exception handling」. Catsbi's DLog. https://catsbi.oopy.io/92cfa202-b357-4d47-8de2-b9b3968dfb2e
음두헌. 「자바 객체지향 프로그래밍 입문」. 에이콘(2022).
'Language > JAVA' 카테고리의 다른 글
[JAVA] 컬렉션 프레임워크 (Java Collection Framework:JCF) 간단하게 알아보기 (0) | 2025.01.20 |
---|---|
응집도(Conhension)와 결합도(Coupling), 그리고 캡슐화의 중요성 (0) | 2025.01.14 |
[자바] 자바 가상 머신, JVM(Java Virtual Machine) 자세히 이해하기 (0) | 2025.01.13 |
객체 지향 5대 원칙 - SOLID 이해하기 (0) | 2025.01.10 |
객체 지향 프로그래밍(Object-Oriented-Programming) 자세히 이해하기 (0) | 2025.01.08 |