[Java] 예외처리 try ~ catch

Bam·2024년 3월 6일
0

Java

목록 보기
45/98
post-thumbnail

오류 error

프로그래밍을 하다보면 숱하게 만나는 오류(error). 그동안 빨간 글씨가 쓰거나 정상 구동에 실패하면 '오류가 발생했네?'라고 했지만 오류가 무엇인지는 정확하게 알지는 못했습니다. 그래서 예외처리를 다루기 전에 오류에 대해 간단하게 알아보고 넘어가보도록 합시다.

프로그램 실행 중 어떤 원인으로 인해 프로그램이 정상적으로 실행되지 않거나 종료되는 경우가 있는데, 이때의 원인을 오류(error)라고 부릅니다.

컴파일시 발생하는 오류를 컴파일 에러, 프로그램 실행 중 발생하는 오류를 런타임 에러라고 구분지어서 부르기도 합니다.


예외 처리

많은 오류들은 컴파일 시에 컴파일러에 의해 발견되어 오류 메세지를 띄워줍니다. 이러한 컴파일 에러들은 빌드 및 배포 전에 소스 코드를 수정함으로써 대부분의 오류를 잡아낼 수 있습니다. 하지만 컴파일러가 발견한 오류 외에도 실행 중에도 다양한 요인으로 인해 런타임 에러가 발생할 수 있습니다.

자바에서 런타임 에러는 다시 오류(error)예외(exception)로 구분합니다.

오류는 하드웨어 등의 문제로 인해 코드상으로 수습할 수 없는 큰 문제를 말합니다. 메모리 부족, 오버플로우가 대표적인 오류입니다. 예외는 발생하면 코드적으로 수습할 수 있는 오류에 비해 간단한 문제를 말합니다.

오류가 발생하면 프로그램이 강제종료되지만, 예외는 예외처리 코드의 유무에 따라서 프로그램의 중단 없이 예외를 해결하고 프로그램을 계속해서 실행할 수 있게 해줍니다.

예외처리(exception ahndling)은 프로그램 실행시에 발생할 수 있는 예외 상황에 대비한 코드를 작성하는 것을 말합니다.


try ~ catch ~ finally

try ~ catch ~ finally구문은 예외 처리를 하기 위한 문법입니다.

try블록에는 예외가 발생할 가능성이 있는 코드를 넣습니다.

catch블록에서는 try 블록에서 예외가 발생했을 경우 처리할 코드를 넣는데, 하나 이상의 catch블록을 넣을 수 있어서 다양한 예외 상황에 대한 처리를 정의할 수 있습니다.

마지막으로 finally블록은 선택사항으로 try~catch의 제일 마지막에 넣어주는 구문입니다. finallytry ~ catch 구문을 수행한 뒤 마지막에 필수적으로 수행해야할 코드를 넣습니다. try 블록에서 예외 발생 여부에 상관없이 무조건 실행됩니다.

정리하자면 try에는 예외 발생 가능성이 있는 코드, catch에는 예외 처리 코드, finally는 예외 처리 후 작업 코드를 넣습니다.

  • 예외가 발생한 경우: try -> catch -> finally
  • 예외가 발생하지 않는 경우: try -> finally
try {
	//예외가 발생할 수 있는 코드
} catch () {
	//예외 처리1
} catch () {
	//예외 처리2
} fianlly {
	//예외 처리후 마지막에 수행할 코드
}

try ~ catch구문은 중첩도 가능합니다.

try {
	try {
    } catch () {}
} catch () {
	try {
    } catch () {}
}

catch()의 괄호 안에 예외 상황을 넣는데, 이 예외는 예외클래스를 이용합니다. 한 번 예시를 통해 더 자세하게 알아보겠습니다.

public class Main {
    static int division (int x, int y) {
        return x / y;
    }

    public static void main(String[] args) {
        int result = division(10, 0);
        
        System.out.println(result);
    }
}

자바에서는 수를 0으로 나누면 예외가 발생합니다.위 코드 실행결과도 0로 나누는 과정에서 ArithmeticException이 발생했다고 알려줍니다. 자 그러면 예외처리를 진행해봅시다.

public class Main {
    static int division (int x, int y) {
        return x / y;
    }

    public static void main(String[] args) {
        int result;

        try {
            result = division(10, 0);
        } catch (ArithmeticException e) {	//예외 클래스와 예외 변수
            System.out.println("예외 발생:" + e);
            result = division(10, 1);
        } finally {
            System.out.println("finally 블록 실행");
        }

        System.out.println(result);
    }
}

try문이 실행되고 예외가 발생하면 catch문의 내용이 실행됩니다. 이때 catch는 예외 클래스를 e에 담아 전달합니다. 이 e를 통해서 예외 클래스의 메소드나 동작을 수행합니다. 반드시 e일 필요는 없지만 해당 catch 구문에서만 e가 유효하기도 해서 암묵적으로 e를 많이 사용합니다.

catch 구문의 수행이 끝나면 finally가 있다면 finally를 수행하고 없다면 예외 처리를 마칩니다.

만약 위 코드에서 에러가 발생하지 않는다면 다음과 같이 실행됩니다.

public static void main(String[] args) {
	int result;

	try {
		result = division(10, 2); // 10/2는 예외가 발생하지 않는 상황
	} catch (ArithmeticException e) {
		System.out.println("예외 발생:" + e);
		result = division(10, 1);
	} finally {
		System.out.println("finally 블록 실행");
	}

	System.out.println(result);
}


예외 클래스

자바에서는 런타임 중에 발생하는 오류(에러와 예외)를 클래스로 정의해놓았습니다. 이 클래스들은 모든 클래스의 조상이 Object의 자손으로 존재하고 있습니다.

그래서 우리는 예외 클래스를 사용하고자 한다면 Exception 클래스 휘하의 자손 클래스 중 상황에 맞는 예외 클래스를 이용핵서 처리할 필요가 있습니다. 물론 예외가 발생했을때 무조건 처리하겠다라고 한다면 Exception만 사용해도 됩니다.

런타임 예외와 그 외 예외 클래스

또한 위 도표에서 Runtime에는 unchecked, Compiletime은 checked라고 표시되어 있는데, 이는 컴파일 시 컴파일러의 체크 유무입니다.

런타임 시 발생하는 런타임 예외는 컴파일 과정에서 체크하지 않아서 실행까지 됩니다. 다만 실행하면 예외를 뱉어버려 프로그램 작동이 멈추게 되고, 컴파일 예외는 컴파일 과정에서 컴파일러에 의해 찾아질 수 있습니다. 즉, 런타임 예외는 컴파일 과정에서 컴파일러에 발견되지 않는다는 것을 명심해두세요.

또한 런타임 예외들은 대부분 프로그래머의 문법적 실수와 관련있고, 그 외의 예외들은 사용자의 잘못된 사용 등 외부 요인으로 인해 발생하게 됩니다. 그래서 런타임 예외들은 try ~ catch를 통한 처리보다는 코드를 수정하는 것이 좋고, 그 외의 예외들에 대해서만 try ~ catch를 사용하는 것이 효율적입니다.

당장 위 예시는 ArithmeticException을 발생시켰는데, 이 클래스는 런타임 예외 클래스의 자손입니다.

예외 처리를 보여주기 위해 try~catch를 사용했지만 실제로는 0을 나누는 부분만을 변경해버리면 코드가 훨씬 간단해지면서 효율도 높아집니다. 이런식으로 런타임 예외들은 코드를 직접 고치는게 더 좋은 경우가 많습니다.


만약 뱉어내는 예외와 예외 클래스가 일치하지 않는다면 어떻게 될까요?

public static void main(String[] args) {
	int result;

	try {
		result = division(10, 0);
	} catch (ArrayIndexOutOfBoundsException e) {
    	/*
        *	실제로 뱉는 예외 ArithmeticException
        *	처리한다고 명시한 예외 ArrayIndexOutOfBoundsException
        *	예외의 종류가 다르다
        */
		System.out.println("예외 발생:" + e);
		result = division(10, 1);
	} finally {
		System.out.println("finally 블록 실행");
	}

	System.out.println(result);
}

분명 예외가 발생했고, 그에 대한 코드도 있지만 예외의 종류가 맞지 않기 때문에 catch문이 작업을 하지 못하게 됩니다. 따라서 catch에서 다루고자 하는 올바른 예외 클래스를 명시해주는 것이 중요합니다.


예외 발생시키기 throw

throw 키워드를 사용하면 프로그래머가 의도적으로 예외를 발생시킬 수 있습니다.

public class Main {
    public static void main(String[] args) {
        try {
            throw new Exception("일부러 예외 발생");
        } catch (Exception e) {
            System.out.println(e);
        }
    }
}


호출위치로 메소드 예외 넘기기 throws

지금까지 다룬 try ~ catch는 예외가 발생한 위치에서 예외를 처리했습니다. 자바에서는 throws 키워드를 통해서 메소드를 호출한 곳에서 예외 처리를 하도록 넘겨줄수도 있습니다.

리턴타입 메소드명 () throws 예외_클래스, ... {}

이렇게 메소드에 예외를 선언하면 호출하는 곳에서 예외를 처리할 수 있게 되고, 메소드를 사용하려는 프로그래머가 선언부만 보고 어떤 예외가 발생하고 처리해야하는 지를 알 수 있게 됩니다.


사용자 정의 예외 클래스

예외 클래스를 사용자가 직접 정의할 수도 있습니다. 이때 일반적인 예외는 Exception 예외 클래스를 상속해 자식으로 만들고, 런타임 예외는 RuntimeException 예외 클래스를 상속해 자식으로 만들어줍니다.

기본적인 형태는 다음과 같습니다.

public class XxxxxException extends Exception 또는 RuntimeException {
	public XxxxxException() {}	//기본 생성자
    public XxxxxException(String Message) { //예외 메세지를 사용하는 생성자
    	super(message);
    } 
}

위 기본 골조를 토대로 사용자 정의 예외 클래스를 하나 만들어봤습니다.

public class SelfmadeException extends Exception {
    public SelfmadeException() {}

    public SelfmadeException(String message) {
        super(message);
    }
}
public class Main {
    public static void main(String[] args) {
        try {
            throw new SelfmadeException("사용자 정의 예외");
        } catch (Exception e) {
            System.out.println(e);
        }
    }
}

0개의 댓글