개발/☕ JAVA

☕ Java 예외 처리

정소은 2024. 7. 26. 14:45

 

 

 

Java의 Exception / Error 처리

 

 

1.  Error와 Exception

 

프로그램 오류란 어떠한 원인에 의하여 프로그램이 비정상적으로 종료되는 것을 의미한다

 

프로그램 오류를 구분하는 기준은 1 ) 발생 시점2 ) '누구'에 의해 발생하는가 두개라고 볼 수 있다

 

발생 시점에 따른 프로그램 오류 구분

컴파일 에러 : 컴파일 시에 발생하는 에러

런타임 에러 : 실행 시에 발생하는 에러

논리적 에러 : 실행은 되지만, 의도와 다르게 동작하는 것

 

누구에 의해 발생하는가에 따른 프로그램 오류 구분

- Error(에러) : 시스템 상에서 발생하는 프로그램 오류 / 프로그램 코드만으로는 수습 불가   

ex ) 메모리 부족(OutOfMemoryError) / 스택오버플로우(StackOverflowError)

- Exception(예외) : 프로그래머의 문법적인 오류 혹은 사용자의 예외 행동에 의한 프로그램 오류 / 프로그램 코드로 수습 가능

ex ) ArithmeticException과 같이 코드상의 오류 / 사용자가 입력 형식에 맞지 않게 입력을 실행한 경우

 

이렇기 때문에 뒷 내용에서는 프로그램 코드를 통해 수습 가능한 Exception에 좀 더 초점을 맞춰보도록 하겠다

 

 

2.  Exception / Error 의 계층 구조

 

 

1 )  Exception과 Error는 Throwable 클래스를 상속받는다

 

Throwable 클래스에서 제공하는 기능은 아래와 같다

//예외의 원인 반환
public String getMessage();

//예외가 발생한 위치와 호출된 메서드 정보 출력
public void printStackTrace();

 

2 )  모든 예외Exception이라는 조상 클래스를 상속받는다

예외는 크게 두가지로 나뉘게 되는데

Exception 클래스를 상속받는 RuntimeException을 함께 상속받는 예외와

오직 Exception 클래스만 상속받는 예외가 있다

 

이때 전자를 Checked Exception, 후자를 Unchecked Exception이라 한다

 

Exception의 종류

( 1 )  Checked Exception : 컴파일러에서 예외 처리를 강제하는 예외

-> 개발 환경에서 빨간 줄

-> 해결하지 않으면 아예 실행이 되지 않음

ex ) IOException, InterruptedException

 

( 2 )  Unchecked Exception : 컴파일러에서 예외 처리를 강제하지 않는 예외

-> 런타임 환경에서 발생하는 예외

-> 예외 처리하지 않아도 일단 실행

ex ) RuntimeError - ArithmetricException, NullPointerException 등등

 

즉, 둘의 차이는 '예외 처리를 하지 않았을 때 컴파일 자체가 되냐 안 되냐'인 것이다

 

 

3.  예외 처리

 

Checked Error의 경우에는 발생했을 때 애초에 컴파일조차 되지 않기 때문에 런타임 환경에서는 발생하지 않을 가능성이 크다.

하지만 Unchecked Error의 경우 런타임 환경에서 발생할 수 있고

이에 대한 예외 처리를 해주지 않으면 프로그램이 비정상적으로 종료되는 상황을 맞이하게 될 것이다.

예외 처리를 해준다면 예외가 발생하더라도 프로그램은 정상적으로 종료된다

 

이제 예외 처리에 대한 문법을 알아보자!

 

1 ) try-catch(-finally) 문

 

try 블럭 안에 예외가 발생할 수 있는 코드를 넣어줌

catch문에 매개변수로 발생 가능한 예외 클래스를 넣어주고 해당 예외가 발생했을 때 행동을 코드로 넣어줌

finally는 예외로 인해 프로그램이 종료되는 상황에서도 무조건 실행해야 하는 코드를 넣어줌

 

public class Test01 {
	public static void main(String[] args) {
    	Scanner sc = new Scanner(System.in);
        
        try {
        	// 예외가 발생 가능한 코드
		int n1 = sc.nextInt(); //InputMismatchException - 예외 예상1
            int n2 = sc.nextInt();
            
            int result = n1 / n2; //ArithmeticException - 예외 예상2
        } catch (InputMismatchException ex) {
        	System.out.println("숫자로 입력해주세요");
        } catch (ArithmeticException ex) {
        	System.out.println("0으로 나누려면 정수로는 안 되는데ㅠㅠ");
        } catch (Exception ex) {    //Exception 클래스 - 예외 클래스들의 조상 클래스
        	System.out.println("대체 무슨 짓을.......?"); // 예상치 못한 다른 에러 잡기
        } finally {
        	System.out.println("마침내");
        }
	}
}

 

컴파일러는 try 블럭 안에 있는 코드를 한 줄씩 읽으면서 예외가 발생하면 해당 예외 클래스 객체를 생성한다

생성된 예외 클래스 객체를 들고 일치하는 catch문을 찾으면 해당 catch 블럭 내 코드를 실행한다

( 이때 일치하는 catch문을 찾는 방법은

try 블럭에서 생성된 예외 클래스와 catch문의 예외 클래스 간에 instanceof를 하면서 true인 catch 블럭을 잡는 것이다 )

try 블럭에서 예외 클래스가 생성되지 않았다면(예외 발생 X) catch문은 수행되지 않는다

마지막으로 예외가 발생하든 안 하든 finally 블럭 내의 코드를 실행시키고 프로그램이 종료된다 ( finally 문은 필수는 아님 )

 

2 )  throw문

 

throw문은 고의로 예외를 발생시키는 데(예외 클래스를 생성하는 데)에 사용된다

 

아래와 같이 쓴다

Exception e = new Exception("고의로 발생시켰음");
throw e;
    
throw new Exception("고의로 발생시켰음");

 

예외를 왜 고의로 발생시키지? 라고 생각할 수 있는데 보통 이런 식으로 사용된다

File createFile(String fileName) throws Exception {
	if (fileName == null || fileName.equals(""))
    	throw new Exception("파일 이름이 유효하지 않습니다"); // 고의로 예외 발생!!!
    File f = new File(fileName);
    f.createNewFile();
    return f;
}

이런 식으로 특정 상황에서 예외를 발생시켜 프로그램을 종료시키게 된다

 

3 )  throws문

 

throws 문을 메서드 자체에 발생 가능한 예외들을 명시하는 데에 사용된다

메서드를 선언할 때 함께 작성해주며 예외가 여러개일 경우 쉼표(,)로 구분한다

public void Exception() throws ArithmetricException, IOException {
	// 메서드 코드
}

 

앞서 말했던 Checked Exception는 throws문이 필수적으로 작성해야 한다 (안 그럼 실행이 안 된다)

Unchecked Exception은 throws문이 필수적이지 않지만 발생 가능한 예외들을 명확하게 명시하기 위해 사용한다

 

 

💡 try-catch문과 throw문에 대한 나의 생각

try-catch문은 타겟 코드 내에서(try 블럭) 발생 가능한 예외 상황들이 여러개이며 예외 발생 조건이 디테일한 경우
통으로 타겟 코드를 묶어버리고 '에라 여기에서 예외 발생하면 알려줘!' 할 때 사용되고

throw문은 예외 발생 상황이 명확할 때 조건문을 통해서 작성하는 것 같다

그동안의 프로젝트들에서는 보통 throw문을 더 많이 사용한 듯..
try 블럭으로 묶어버리는 것보다 메서드 단위로 다루는 게 더 편하기도 하구

 

 

4 )  사용자 정의 예외

 

어플리케이션을 개발하다 보면 Java에서 제공하는 예외 클래스들 말고도 다양한 예외 상황들이 굉장히 많다

예를 들어 회원가입을 하는 상황에서 사용자의 아이디가 8자 이상, 숫자 및 특수 문자 필수 포함이라는 조건이 있는 경우

이 조건들을 만족하지 않는 아이디를 사용자가 입력했을 때 예외를 던져줘야 한다

하지만 이런 예외들은 Java에서 클래스로 제공하고 있지 않기 때문에 프로그래머가 자체적으로 예외 클래스를 정의해야 한다

 

프로그래머가 예외 클래스를 자체적으로 작성할 경우 보통 RuntimeException을 상속받는 경우가 많다 (다형성!!)

 

예시

class MyException extends RuntimeException {
	private final int ERR_CODE;
    
    MyException(String msg, int errCode) {
    	super(msg);
        ERR_CODE = errCode;
    }
    
    public int getErrCode() {
    	return ERR_CODE;
    }
}