본문 바로가기

develop/Java

예외(Exception)

 

-개발자가 의도하지 않은 상황에서 발생하는 문제

-예외 발생 시 프로그램이 비정상적으로 종료된다.

-오류는 시스템적인 문제 발생으로 개발자가 해결 불가능한 영역이지만 예외는 오류와 달리 심각도가 낮고,

예외 처리를 통해 프로그램의 비정상적인 종료를 막을 수 있다.

=> 예외가 발생하지 않도록 예외 발생 코드를 수정하는 것은 예외 처리가 아니며, 

예외가 발생한다고 가정했을 때 해당 상화에서 수행할 작업을 기술하는 것이 예외 처리

-컴파일(번역) 시점에서 예외 발생 여부를 미리 파악하고 예외 처리를 검사하는 Compile checked Exception 계열이라고 함.

=> 이 계열의 경우 예외가 발생할 것으로 예상되는 코드에 예외 처리가 되어 있지 않으면 컴파일러에 의해 

예외 처리 요청에 해당하는 오류를 표시하게 됨.

-컴파일 시점에서는 예외 발생 여부를 알 수 없고, 실행 시점에서 예외가 발생하며, 예외 처리 여부를 검사하지 않는 

예외를 complie unchecked Exception 계열이라고 함.

(ex. RuntimeException 계열 - NumberFormatException, ArithmeticException 등)

 

	int num1 = 3, num2 = 0;
//		System.out.println(num1 / num2); // 예외 발생
		// => 나눗셈의 두번째 피연산자가 0 일 경우 ArithmeticException 예외 발생
		// Exception in thread "main" java.lang.ArithmeticException: / by zero 
		// at Ex3.main(Ex3.java:22)
		// => 0 에 의한 나눗셈으로 인해 ArithmeticException 예외가 발생했으며 
		//    예외 발생 위치가 Ex3 클래스의 main() 메서드 내에 22번 라인 코드
		// => 예외가 발생하면 프로그램은 비정상적으로 종료됨
        
        
	int[] arr = {1, 2, 3}; // 0 ~ 2번 인덱스까지 부여됨
//		System.out.println(arr[3]); // 예외 발생
		// => 배열 범위를 벗어난 인덱스 접근 시 예외 발생
		// Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 3
		// at Ex3.main(Ex3.java:33)
        
        
	String str = null;
//		System.out.println(str.length()); // 예외 발생
		// => 참조 변수값이 null 일 경우 변수 접근 시 예외 발생
		// Exception in thread "main" java.lang.NullPointerException
		// at Ex3.main(Ex3.java:40)
		
		
		String strNum = "30.5";
//		int iNum = Integer.parseInt(strNum); // 예외 발생
		// => 변환 불가능한 수치데이터를 변환 시 예외 발생
		// Exception in thread "main" java.lang.NumberFormatException: For input string: "30.5"
		// at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
		// at java.lang.Integer.parseInt(Integer.java:580)
		// at java.lang.Integer.parseInt(Integer.java:615)
		// at Ex3.main(Ex3.java:47)        

 

 

 

예외 처리 (Exception Handling)

 

- 예외가 발생하더라도 프로그램이 정상적으로 종료될 수 있도록 예외 상황에 대한 추가적인 작업(해결책)을 수행하는 것

- try ~ catch 블록을 사용하여 예외 처리 작업 수행

=> try 블록 내에서 예외 발생 가능성이 있는 코드들을 기술하고 예외가 발생하면 JVM에 의해 

해당 예외 객체가 생성되어 전달되며 catch 블록 중 일치하는 예외 타입을 찾아 해당 블록을 실행하게 됨

=> 만약, 일치하는 catch 블록이 없을 경우 프로그램은 비정상 종료된다

- 하나의 try 블록에서 catch 블록은 여러개를 가질 수 있으며 예외 발생시 위에서부터 차례대로 catch 블록을 검사함

- 예외클래스를 각각의 catch 블록으로 구분해도 되고, 업캐스팅을 활용하여 상위 예외 타입으로 결합하여 처리해도 된다

(ex. ArithmeticException & NullPointerException 을 결합하여 RuntimeException 이나 Exception 타입으로 처리 가능)

 

 < 기본 문법 >
 try {
 		// 예외가 발생할 것으로 예상되는 범위의 코드들...
 } catch(예외클래스명1 변수명) {
 		// 예외클래스명1 에 해당하는 예외 발생 시 실행할 코드들...
 } catch(예외클래스명2 변수명) {
 		// 예외클래스명2 에 해당하는 예외 발생 시 실행할 코드들...
 } catch(Exception e) {
 		// 위의 예외클래스 외의 모든 예외 처리가 가능한 블록
      // 단, 다른 예외클래스보다 위에 있을 경우 
      // 다른 예외클래스가 catch 되지 못하므로 문제 발생!
 }

 

 

 

finally 블록 (try ~ catch ~ finally)

 

- 예외 발생 여부와 관계없이 무조건 수행해야하는 문장을 기술

ex) 데이터베이스 자원 반환(close()) 하는 작업 등

 

- 심지어 실행 코드(try 또는 catch 블록) 내에서 return 문을 만나더라도 

finally 블록 내의 코드는 무조건 실행 된 후 return 문이 실행된다. 

(메서드를 빠져나가기 전 finally 블록 실행 후 빠져나감)

 

public void exceptionMethod() {
		System.out.println("메서드 시작!");
		
		try {
			System.out.println("try 블록 시작!");
			
			String str = null;
			System.out.println(str.length()); // NullPointerException 예외 발생
			
			System.out.println("try 블록 끝!");
			return; // 현재 메서드 종료 후 호출한 곳으로 돌아감
			// => 예외 발생 시 실행되지 못하는 문장
		} catch(NullPointerException e) {
			System.out.println("NullPointerException 예외 발생 시 처리할 코드");
		}
				
		// 예외 발생 여부와 관계없이 실행되는 문장이지만
		// try 블록 또는 catch 블록 등에서 return 문을 만나면
		// 메서드가 종료되는데 이 때 try 블록 밖의 문장은 실행되지 않고
		// 바로 메서드를 빠져나가게 된다!
		System.out.println("메서드 종료!");
	}
	
	public void exceptionFinallyMethod() {
		System.out.println("메서드 시작!");
		
		try {
			System.out.println("try 블록 시작!");
			
			String str = null;
//			System.out.println(str.length()); // NullPointerException 예외 발생
			
			System.out.println("try 블록 끝!");
			return; // 현재 메서드 종료 후 호출한 곳으로 돌아감
			// => 예외 발생 시 실행되지 못하는 문장
		} catch(NullPointerException e) {
			System.out.println("NullPointerException 예외 발생 시 처리할 코드");
		} finally {
			// 예외 발생 여부와 관계없이 실행되어야하는 코드를 기술
			// try 또는 catch 문에서 return 문을 만나더라도
			// 메서드를 종료하고 빠져나가기 전 finally 블록의 문장을 실행한 후
			// 메서드를 종료하고 빠져나감
			System.out.println("예외 발생 여부와 관계없이 실행되는 코드");
		}
				
		// 예외 발생 여부와 관계없이 실행되는 문장이지만
		// try 블록 또는 catch 블록 등에서 return 문을 만나면
		// 메서드가 종료되는데 이 때 try 블록 밖의 문장은 실행되지 않고
		// 바로 메서드를 빠져나가게 된다!
		System.out.println("메서드 종료!");
	}

}

 

 

예외 처리에 대한 위임

 

- 자신의 위치에서 예외가 발생했을 때 자신이 직접 처리하지 않고 자신의 메서드를 호출한 곳으로 예외를 위임(떠넘기기=전달)

- throws 키워드를 사용하여 예외 처리를 위임(전달)

- 예외처리클래스를 지정할 떄 각각의 예외를 따로 지정하거나 상위 타입 예외 클래스를 사용하여 통합으로 관리도 가능함

ex) ArithmeticException + NumberFormatException = RuntimeException
      RuntimeException + ClassNotFoundException = Exception

 

- 예외 처리를 위임받은 곳에서는 예외 처리에 대한 책임이 발생하여 직접 처리하거나 또 다시 위임 가능

(폭탄을 전달받았으면 폭탄을 해체하거나 다른 사람한테 넘기는 것 처럼)

- 최종적으로 main() 메서드까지 위임될 경우 main() 메서드에서 처리 필수!

 

<기본 문법>

메서드선언부() throws 위임할예외클래스명1, ..., 예외클래스명n{

      //예외발생코드

      //예외 발생 시 throws 키워드 뒤에 명시된 클래스와 일치하는 클래스가 있으면 해당 예외는 메서드 호출한 곳으로 던진다.

}

 

최종적으로 main 메서드까지 위임되면 메서드 내에서도 메서드 호출 코드에 대한 책임이 발생함

main에서도 throws 키워드를 사용할 수는 있으나 최종적으로 처리할 곳을 확인 불가능하므로

main 메서드 내에서 try ~ catch를 통해 직접 처리해야 함.

 

	try {
			부장();
		} catch (Exception e) {
			System.out.println("main() 메서드가 모든 예외를 처리!");
		}
		
	}
	
	public static void 부장() throws Exception {
		// 1. 위임받은 2개(or 3개)의 예외를 직접 처리할 경우
		// => ClassNotFoundException 은 컴파일 시 체크되므로 예외 표시됨
//		try {
//			과장();
//		} catch (ClassNotFoundException e) {
//			System.out.println("부장이 ClassNotFoundException 처리");
//		} catch (RuntimeException e) {
//			System.out.println("부장이 RuntimeException 처리");
//		}
		
		// 2. 위임받은 예외를 다시 위임할 경우
		// => 모든 예외를 하나의 묶음으로 다루려면 Exception 클래스 사용 가능
		과장();
	}
	
	public static void 과장() throws ClassNotFoundException, RuntimeException {
		// 1. 위임받은 2개의 예외를 직접 처리할 경우
//		try {
//			System.out.println("대리로부터 ArithmeticException, NumberFormatException 위임됨");
//			대리();
//		} catch (RuntimeException e) {
//			// ArithmeticException 과 NumberFormatException 을 따로 처리하거나
//			// 상위 타입으로 묶어서 처리 가능함
//			System.out.println("위임받은 예외를 RuntimeException 타입으로 처리");
//		}
		
		// 2. 위임받은 예외를 다시 부장에게 위임할 경우
			System.out.println("대리로부터 ArithmeticException, NumberFormatException 위임됨");
			대리();
			Class.forName("클래스명"); // ClassNotFoundException 예상 코드
	}
	
	public static void 대리() throws ArithmeticException, NumberFormatException {
		// 사원으로부터 위임된 예외를 전달받을 경우
		// 위임된 예외 클래스에 대한 예외 처리를 수행해야한다.
		// => 만약, Compile Checked Exception 일 경우 메서드 호출 코드에
		//    예외 처리 책임에 따른 오류가 표시되지만
		//    Compile Unchecked Exception 일 경우 메서드 호출 코드에
		//    오류가 표시되지 않는다.
		
		// 1. 대리가 사원으로부터 위임된 예외를 직접 처리할 경우
//		try {
//			System.out.println("사원으로부터 ArithmeticException 위임됨");
//			사원();
//		} catch (ArithmeticException e) {
//			System.out.println("대리가 ArithmeticException 직접 처리!");
//		}
		
		// 2. 예외를 직접 처리하지 않고 호출한 곳으로 위임할 경우
		Integer.parseInt("숫자가아닌데이터"); // NumberFormatException 발생 코드
		사원(); // ArithmeticException 예외가 위임되는 코드
		// => 현재 위치에서 발생할 것으로 예상되는 예외 : 2개
		//    ArithmeticException 과 NumberFormatException 모두 위임하거나
		//    선택적으로 처리하고 나머지만 위임 가능
		
	}
	
	public static void 사원() throws ArithmeticException {
//		try {
//			System.out.println("사원에서 예외 발생");
//			System.out.println(3 / 0); // ArithmeticException 발생 예상 코드
//		} catch (ArithmeticException e) {
//			System.out.println("사원이 직접 예외 처리");
//		}
		
		// 예외를 호출한 곳(대리)으로 위임할 경우
		System.out.println("사원에서 예외 발생");
		System.out.println(3 / 0); // ArithmeticException 발생 예상 코드
		// => 직접 try~catch 블록을 통해 예외를 처리하지 않고 위임할 경우
		//    메서드 선언부 마지막 부분에 throws 키워드를 쓰고
		//    뒷부분에 위임할 예외클래스 이름을 명시함
		//    (1개 또는 복수개의 클래스 지정 가능)
	}

 

 

개발자에 의한 예외 발생(예외를 직접 발생시키기)

 

- 자바 문법에서 정해놓은 규칙에 따른 예외가 아닌 개발자 입장에서의 논리적인 예외 상황이 발생했을 때 예외를 발생시킬 수 있음.

ex) 점수를 int형 score 변수에 입력받았을 때 100보다 클 경우 예외
=> 문법적으로는 맞지만, 논리적으로 틀렸을 때 예외 발생

- throw 키워드를 사용하여 발생시킬 예외 클래스의 객체 생성하여 발생

 

< 기본 문법 >

예외 발생 상황에서(ex. if문, while문 등의 조건에서)

throw new 예외클래스명("예외메세지");

 

// inputScore() 메서드 호출 시 Exception 예외가 throws 되므로
		// 메서드 호출 시점에서 예외 발생 가능성을 파악할 수 있고
		// 호출 코드에서 try~catch 블록을 사용하여 예외를 처리해야한다!
		try {
			inputScore(150);
		} catch (Exception e) {
			e.printStackTrace();
			System.out.println("예외 발생 메세지 : " + e.getMessage());
		}
		
	}
	
	public static void inputScore(int score) throws Exception {
		// 성적 처리를 위해 점수를 입력받은 메서드에서
		// 0 ~ 100 사이의 점수만 입력받아 처리한다고 가정
		// => int형 변수는 문법적으로 0 ~ 100 범위 외의 정수도 무관함
		//    이 때, 0 ~ 100 범위가 아닌 정수 입력 시 예외를 발생시킨 후
		//    해당 예외를 외부로 던져서 예외 처리를 강제로 수행시킬 수 있다.
		
		// if문을 사용하여 0 ~ 100 사이 범위가 아닌 점수가 입력됐을 때 판별
		if(score < 0 || score > 100) {
			// 문법적로는 올바른 문법이지만 논리적으로 문제가 있으므로
			// 개발자가 직접 예외를 발생시켜야 한다!
			throw new Exception("0 ~ 100 사이의 점수만 입력하세요!");
			// => 개발자가 예외 상황을 정의하고 직접 예외를 발생시킴
			// => 이 때, 발생한 예외를 직접 처리(try~catch)해도 되지만
			//    주로, 발생한 예외를 외부로 던지기(위임하기)위해 throws 사용
		}
		
	}

'develop > Java' 카테고리의 다른 글

Wrapper 클래스  (0) 2021.04.18
enum 타입  (0) 2021.04.12
중첩 클래스  (0) 2021.04.12
제네릭(Generic, 일반화)  (1) 2021.04.05
Collection Framework(컬렉션 프레임워크)  (0) 2021.04.04