저를 포함한 초보자분들에게 도움이 됐으면 좋겠습니다.
초보자분들은 수박 겉핥기 식으로 알고 넘어가지 마시고, 조금이라도 왜 이렇게 프로그램이 돌아가는지 알고 가시는 시간이 됐으면 좋겠습니다.

오늘은 예외 처리에 대해서 알아봅시다. 프로그래밍을 하다 보면 다양한 오류가 발생하기 쉽습니다.
가장 대표적인 예로는 file을 읽으려고 했는데 해당 file이 없는 경우, 0으로 나누는 경우 등등.. 쉽게 접할 수 있는 오류들이 많이 있죠. 이러한 오류를 처리하는 것을 '예외 처리'라 볼 수 있습니다.
그럼 어떻게 예외 처리를 할 수 있는지 살펴보도록 합시다.

자, 일단 오류를 분류해 봅시다. 위에서 간단하게 2가지 오류를 말씀드렸습니다.
첫 번째는 파일이 존재하지 않는 경우, 두 번째는 0으로 나누었을 때 오류입니다. 둘 다 오류지만 자세히 살펴보면 특징이 조금 다릅니다. 첫 번째 파일이 존재하지 않는 경우의 오류는 프로그래머가 예상을 하고 예외 처리를 해줘야 하는 경우입니다. 즉, 파일이 존재하지 않을 경우를 대비해서 예외 처리를 해줘야 한다는 것이죠. 예측 가능합니다. 
두 번째, 0으로 나누었을 때 오류입니다. 첫 번째처럼 예외 처리를 해 줄 수 있습니다. 하지만 '0으로 나눈다'를 어떤 식으로 예외 처리를 해줄 건가요? 단적인 예를 들어보죠.

(1)	public void Exam() {
		for(int i = 9; i >=0 ;i++)
			System.out.println(10 % i);
	}

(1) 코드와 같이  10을 9~0까지 나눈 나머지 값을 출력한다고 칩니다. 그럼 예외 처리를 어떻게 해야 할까요? 예외 처리되는 부분에 다시 'for(int i = 9; i >0 ;i++)'를 수행하여 출력해야 합니다. 생각해보세요. 예외 처리를 하는 것이 좋을까요? 아니면 (1) 코드를 수정하는 것이 좋을까요? 당연히 (1) 코드를 i>0으로 수정하는 게 무조건 좋습니다. 두 번째 케이스는 '버그'에 가깝다고 보시는 것이 맞죠.
그래서 첫 번째 케이스는 '검사 예외'(- 예외 처리를 해줘야 하는 것)라 하고 두 번째 케이스는 '비 검사 예외(- 예외 처리보단 버그에 가까워 코드를 고치는 것)'이라고 부릅니다. (물론 비검사 예외도 예외 처리를 해 줄 수 있습니다.)

자 이제 본격적으로 예외 처리를 할 수 있는 try/catch 구문에 대해서 알아봅시다.
try는 예외를 감지하는 곳이고 catch는 try에서 감지한 예외를 처리하는 부분입니다.
예를 보시는 게 더 빠를 것 같습니다. 간단한 예를 위해서 '비검사 예외'를 사용하겠습니다.


(2) public void A() {
		try {
		System.out.println( 10 / 0 );
        System.out.println( "끝" );
		}
		catch(Exception e) {  //catch(어떤 예외?) -Exception은 모든 예외
			System.out.println("예외 처리"); //출력
		}
	} //최종 출력 : 예외 처리

(2)와 같은 구문이 있습니다. 10을 0으로 나누고 있어서 예외가 발생하게 되죠.
이처럼 예외가 발생할 수 있는 부분을 try{ } 안에다 감싸줍니다. 그리고, 예외가 발생하면 즉시 catch 구문으로 넘어가 예외 처리를 수행하게 됩니다. 굉장히 간단합니다. try 구문에서 예외가 발생하면 catch 문으로 가서 해당 예외 처리를 하는 거죠. 하지만 주의해야 할 점이 있습니다. 
(2)의 최종 출력은 "예외 처리"입니다. try 안에 "끝"이라는 출력이 있음에도 출력하고 있지 않습니다.
눈치채셨겠지만, 예외가 발생한 지점에서 바로 catch 부분으로 넘어가는 것이죠. 즉, 10/0에서 예외가 발생해 catch 구문으로 넘어가 예외 처리를 하므로 "끝"이라는 글자가 출력이 되지 않은 것이죠.
또 (2) 코드의 catch는 Exception을 매개변수로 사용하고 있습니다. Exception은 모든 예외를 나타내는 것입니다. 하지만 각각의 예외마다 처리하는 방식이 다를 수 있으므로 예상되는 예외를 매개변수에 넣어서 각각 알맞게 처리해 주시는 게 좋습니다.
(3) 코드와 같이 말이죠.

(3)public static void main(String[] args) {
		int x,sum;
		try {
			x = Integer.parseInt(args[0]);
			sum = x / 0;
		}catch(ArithmeticException e) { // 첫번째 예외처리
			System.out.println("0으로 나누어서 예외발생");
		}catch(Exception e ) { // 두번째 예외처리
			System.out.println("args[0]를 주지않아 예외발생");
		}
	}

(3) 코드와 같이 여러 가지 예외 처리를 선언할 수도 있으니, 자신이 처리해야 하는 예외들을 찾아보시고 알맞게 처리해 주는 게 필요합니다. 하지만 주의하실 점은 catch 구문도 순서가 있기에, 맨 처음부터 
exception(모든 예외)로 선언해 준다면, 두 번째, 세 번째... 선언한 catch 문은 수행할 수가 없습니다.
주의하세요.

자, 그럼 예외 처리를 살펴보았으니, 예외를 인위적으로 발생시켜 보죠.
예외를 인위적으로 발생시키는 방법은 throw라는 키워드가 있습니다.
throw는 '던진다' 즉 예외를 발생시키는 것이라 생각하시면 됩니다. 이것도 예시로 살펴보죠.


(4)	public void A() {
		try {
		 throw new Exception();
		}
		catch(Exception e) {
			System.out.println("A에서 예외 처리");
		}
	} //최종 출력 : A에서 예외 처리

간단합니다. new 예와 명();를 하여 객체를 생성하여 <throw 객체>로 예외를 발생시킵니다. 
이렇게 발생한 예외는 알맞은 catch 문에 들어가 예외 처리를 할 수 있죠. throw는 사람이 '인위적'으로 발생시키는 예외입니다. 반드시 처리해줘야 할 예외를 명시적으로 예외를 발생시켜, 유지 보수에 도움을 주기도 합니다.

마지막으로 throws라는 키워드입니다. throws는 < 메소드명() throws 예외명 > 형태로 사용하고, 자신을 불러온 메소드에서 예외를 던진다 정도로 생각하시면 됩니다. 예를 봅시다.


(5)	public void A() {
		try {
			B();
		}
		catch(Exception e) {
			System.out.println("A에서 예외 처리");
		}
	}
	
	public void B() throws Exception{
		throw new Exception();
	}

A메소드에서 B메소드를 호출하고 B메소드는 예외를 throw 하고 있습니다. 하지만 B에서는 try/catch 구문이 없어서 예외를 발생시켜도 처리해줄 곳이 없습니다. 이런 경우는 대게 '호출한 곳에서 처리해주겠다'라고 생각하시면 됩니다. 즉, 발생한 예외를 호출한 곳으로 넘겨주는 것이 throws입니다.
(5) 코드와 같이 throws를 해주게 되면, 인위적으로 생성한 예외가 A메소드로 넘어가 catch 부분에서 예외 처리가 되는 것이죠. throws 키워드는 메소드에서 발생할 수 있는 예외들을 나열하여 적어줌으로써 호출한 메소드에서 적절하게 대비를 하도록 유도하는 역할도 합니다.
throw, throws를 잘 구분하여 기억하시고 마지막으로 finally를 살펴보고 마무리 짓겠습니다.

finlly는 try/catch 구문을 수행하고 '반드시'수행되는 구문입니다. 예를 보시죠.


(6)	public void A() {
		try {
			System.out.println("1");
			System.out.println( 10 / 0); // 예상치못한 예외발생
			System.out.println("2");
		}
		catch(Exception e) {
			System.out.println("3");
		}
		finally {
			System.out.println("4");
		}
	} //최종 출력 : 1 3 4

(6) 코드를 봅시다. finally 구문은 catch 다음에 구현하여 사용합니다. finally 구문은 말씀드렸듯 반드시 수행되는 구문입니다. 최종 출력을 보시면 1 3 4 가 나옵니다. 즉, 0으로 나누는 것에 예외가 발생하고
finally 구문을 수행했다는 것이죠. 정말 간단하고 알아 두시기만 하면 됩니다.
그럼 finally 구문은 왜 사용할까요? 이유는 간단합니다. 1. 반드시 수행되야 하는 구문이 있을 경우,
2. try 구문, catch 구문의 중복되는 코드를 finally 구문에 넣음으로써 코드 중복을 없앨 수 있습니다. 예를 들어볼까요? Scanner가 대표적입니다.


(7)	public void A() {
		Scanner test = new Scanner(System.in);
		try {
			//~~~~~~~ 기타 구문 수행
			test.close();
		}
		catch(Exception e) {
			test.close();
		}
	}

(7) 코드를 보시면 test로 Scanner가 선언되어있습니다. 만약 try 구문에서 예외 없이 마친다면 test.close로 Scanner를 끝내야겠죠. 하지만 try 구문 안에 ~~~~구문에서 예외가 발생한다면 어떨까요? 그럼 try 구문에서 close를 해주지 못했으니 catch 구문에 가서 똑같은 코드인 test.close를 해 줘야 합니다.
하지만 finally 구문을 사용하게 된다면, 예외가 발생하든 안 하든 반드시 수행해야 하므로 test.close의 코드 중복을 막고 보다 보기 편하게 코딩할 수 있습니다.

자, 지금까지 try/catch 예외 처리에 대해서 알아보았습니다. 어렵지 않은 내용입니다. 어떻게 사용하는지만 알고 있으면 충분히 응용하여 사용하실 수 있다고 생각 듭니다. thow, thows는 헷갈리지 않게 다시 한번 정리해 두시는 것이 좋겠습니다.

자. 다음 편은 쓰레드에 대해서 적어보도록 하겠습니다.

+ Recent posts