본문 바로가기

Languages/JAVA

[JAVA] 예외처리 (Exception) / try ~ catch ~ finally / 직접 예외 처리(throw , throws)

예외처리하는 법을 알아봅시다 🥰


오늘은 자바에서 예외처리하는 방법을 알아봅시다.

 

프로그램을 만들다 보면 에러가 날때가 많습니다. 에러가 났을때 그에 맞는 적절한 처리를 하고 싶을때 자바가 제공해주는 문법이 있습니다. try, catch, throw 등을 사용해서 에러를 처리 할 수 있도록 해줍니다.

 

오류가 발생하는 코드를 보면서 예외처리하는 법을 보겠습니다!


1. 숫자를 문자열로 입력받아 숫자로 바꾸고 100을 더해서 출력하는 예제

Scanner scan=new Scanner(System.in);
System.out.print("숫자 입력:");
//숫자 형식의 문자열을 입력 받는다. "10" "20" "10.1" 등등
String inputNum=scan.nextLine();
double num=Double.parseDouble(inputNum);
//변환한 숫자에 100 을 더하고
double result = num+100;
//더한 결과 값을 출력한다.
System.out.println("입력한 숫자 + 100 :"+result);

숫자를 문자열로 입력받아 숫자로 바꾸고 100을 더해서 출력하는 코드입니다.

 

올바른 숫자를 입력하면 출력값이 잘 나오는 것을 볼 수 있습니다.

 

하지만 이렇게 숫자가 아닌 문자를 입력한다면 숫자로 변환하는 과정에서 오류가 발생하게 됩니다.


어디에서 오류가 발생하는지 알았으니 예외처리를 해봅시다.

try {
    ...
} catch(예외1) {
    ...
} catch(예외2) {
    ...
...
} finally {
	...
}

 

오류가 났을때 사용하는 try, catch 문 기본 구조입니다.

try문 안에서 오류가 발생하면 예외에 해당되는 catch문이 실행됩니다. 만약 오류가 발생하지 않으면 catch문은 실행되지 않습니다. catch문은 여러개를 만들 수 있습니다.
finally문은 try 문에서 예외발생 여부에 관계없이 무조건 실행되는 구문입니다.

 

Scanner scan=new Scanner(System.in);
System.out.print("숫자 입력:");
//숫자 형식의 문자열을 입력 받는다. "10" "20" "10.1" 등등
String inputNum=scan.nextLine();
try {
	//입력한 문자열을 실제 숫자(실수) 로 변환한다.
	double num=Double.parseDouble(inputNum);
	//변환한 숫자에 100 을 더하고
	double result = num+100;
	//더한 결과 값을 출력한다.
	System.out.println("입력한 숫자 + 100 :"+result);
}catch(NumberFormatException nfe) { //NumberFormatException type의 예외가 발생하면
	//실행되는 블럭

	//예외 정보를 콘솔에 출력하기
	nfe.printStackTrace();
}

try, catch 문 사용법을 알았으니 위의 예제를 예외처리를 해봅시다.

문자열을 숫자로 변활 할 때 java.lang.NumberFormatException 오류가 발생하는 것을 확인했습니다.

try문에 숫자로 변환하는 코드를 작성해주고 catch문에 NumberFormatException 예외를 넣어줍니다.

printStackTrace() 메소드는 예외 정보를 콘솔에 출력해줍니다.

 


다른 예제도 보겠습니다. 

2. 나눌 수와 나누어 지는 수를 입력받아서 몫과 나머지를 구하는 예제

Scanner scan=new Scanner(System.in);
System.out.print("나눌수 입력:");
String inputNum1=scan.nextLine();
System.out.print("나누어 지는 수 입력:");
String inputNum2=scan.nextLine();


//입력한 문자열을 정수로 변환하고 
int num1=Integer.parseInt(inputNum1);
int num2=Integer.parseInt(inputNum2);
//num2 를 num1 으로 나눈 몫
int result=num2/num1;
//num2 를 num1 으로 나눈 나머지
int result2=num2%num1;

System.out.println(num2+" 를  "+num1+" 으로 나눈 몫:"+result);
System.out.println(num2+" 를  "+num1+" 으로 나눈 나머지:"+result2);	



System.out.println("main 메소드가 정상 종료됩니다.");

나눌 수와 나누어 지는 수를 입력받아서 몫과 나머지를 구하는 예제입니다.


분모에 0 이 아닌 숫자를 입력하면 정상적이게 작동합니다.

 

하지만 이렇게 0으로 나눌경우에는 java.lang.ArithmeticException 오류가 발생합니다.

 

또한 숫자가 아닌 수를 입력할 경우에도 java.lang.NumberFormatException 오류가 발생합니다.


이제 예외처리를 해봅시다.

Scanner scan=new Scanner(System.in);
System.out.print("나눌수 입력:");
String inputNum1=scan.nextLine();
System.out.print("나누어 지는 수 입력:");
String inputNum2=scan.nextLine();

try {
	//입력한 문자열을 정수로 변환하고 
	int num1=Integer.parseInt(inputNum1);
	int num2=Integer.parseInt(inputNum2);
	//num2 를 num1 으로 나눈 몫
	int result=num2/num1;
	//num2 를 num1 으로 나눈 나머지
	int result2=num2%num1;

	System.out.println(num2+" 를  "+num1+" 으로 나눈 몫:"+result);
	System.out.println(num2+" 를  "+num1+" 으로 나눈 나머지:"+result2);	

	System.out.println("작업 성공입니다.");
}catch(NumberFormatException nfe) {
	nfe.printStackTrace();
}catch(ArithmeticException ae) {
	ae.printStackTrace();
}finally { //예외가 발생 하던 안하던 실행이 보장되는 블럭
	System.out.println("무언가 마무리 작업을 합니다.");
}

System.out.println("main 메소드가 정상 종료됩니다.");

오류가 발생할만한 문장은 모두 try로 넣어주었습니다. NumberFormatException과 ArithmeticException 오류를 예외처리해준 것을 볼 수 있습니다.

finally문으로 예외 발생 여부에 관계없이 마무리 작업을 한다는 내용을 출력해주었습니다.

이렇게 오류가 발생해도 finally구문은 정상 처리되는 것을 볼 수 있습니다.


직접 예외를 발생시키기 ( throw / throws ) 

이번에는 직접 예외를 발생시켜봅시다.

throw / throws 예외 객체를 생성하면 직접 예외를 생성 할 수 있습니다.

0~3까지 랜덤한 숫자가 나오게하고 0이면 졸려서 공부를 할 수 가 없다는 예외를 발생시키는 프로그램을 구현해볼 것입니다.

public class SleepyException extends Exception{
	
	//예외 메세지를 생성자의 인자로 전달받아서 부모 생성자에 전달하기
	public SleepyException(String msg) {
		super(msg);
	}

}

SleepyException 클래스를 하나 만들어줍니다.

Exception을 상속해줍니다.

이때 Exception 말고도 RuntimeException을 상속받을 수 있습니다.

 

Exception vs RuntimeException

Exception 을 상속 받은 Exception 종류는 반드시  try ~ catch 블럭으로 묶어줘야합니다. 
RuntimeException 을 상속 받은 Exception 종류는 try ~ catch 블럭으로 묶어주지 않아도 문법 오류가 발생하지 않습니다. 따라서 필요시 선택적으로 try ~ catch 블럭으로 묶어주면 됩니다.  

 

예제는 Exception을 상속받아 반드시  try ~ catch 블럭으로 묶어야하게 만들었습니다. 

클래스 안에서는 생성자를 하나 만들어 주었는데, 예외 메세지를 생성자의 인자로 전달받아서 부모 생성자에 전달하는 생성자를 만들어주었습니다. 

 


public class StudyUtil {
	
	
	//공부하는 메소드
	public static void study() throws SleepyException {
		//랜덤한 숫자를 얻어내서
		Random ran = new Random();
		int ranNum = ran.nextInt(3);
		//우연히 0이 나오면 예외를 발생시키기
		if(ranNum==0) {
			throw new SleepyException("너무 졸려서 공부를 할 수가 없어요!");
		}
		System.out.println("열심히 공부합니다!!");
	}
}

StudyUtil 클래스에서 예외를 만들어봅시다.

0이 나오면 throw new SleepyException() 구문을 사용하여 해당 오류가 나오도록 합니다.

study 메소드 이름 옆에는 메소드에 포함된 SleepyException을 표기해줍니다.(throws)


System.out.println("main 메소드가 시작되었습니다.");

try {
	StudyUtil.study();
} catch (SleepyException e) {
	// TODO Auto-generated catch block
	e.printStackTrace();
}

main 메소드에서 확인해보겠습니다.

예외가 일어나지 않았을때 정상작동 되는 모습을 볼 수 있습니다.

또한, 너무 졸려서 공부를 할 수가 없다는 SleepyException 오류가 발생하는 모습도 볼 수 있습니다.