본문 바로가기

JAVA/Study

[JAVA] 예외(Exception)와 예외 클래스(Class Exception)

▒ JAVA에서의 에러 개념

일반론적 에러의 분류

  • 컴파일 에러(compile error) : 프로그램 코드 작성 중 발생하는 문법적인 오류
  • 런타임 에러(runtime error) : 실행 시에 발생하는 오류 (프로그램이 중지되는 오류)
  • 논리적 에러(logical error) : 정상적으로 실행은 되지만, 프로그램이 의도와 다르게 동작(bug)하는 것

JAVA에서의 에러와 예외

  • 에러(error) : 컴퓨터 HW상의 문제로 인하여 응용프로그램 실행 오류가 발생하는것으로서, 프로그램 코드로 수습할 수 없는 원시적인 오류이기 때문에 에러가 발생하면 프로그램은 즉시 종료됨
    (OutOfMemoryError, ThreadDeath, StackOverflowError 등)
  • 예외(exception) : 에러(error) 이외에 프로그램 자체에서 발생하는 오류로서 원칙적으로 오류가 발생되면 프로그램이 종료되지만, 예외 처리(exception handling)를 통해 정상 실행 상태를 유지할 수 있음
    (읽어들이려는 파일이 존재하지 않거나, 네트워크와 DB연결이 안 되는 경우 등)

예외클래스(Class Exception)의 구조

 이클립스와 같은 IDE 프로그램을 사용해 코드를 작성하다보면 사소한 오기나 자료형 불일치 부터 시작하여 unreachable code와 같은 구조적 에러에 이르기까지 다양한 에러들이 즉시 표시되는 것을 볼 수 있다. 이는 IDE 프로그램이 작성중인 코드를 검사하여 에러를 잡아내기 때문이다. 이는 컴파일 에러(compile error) 개념에 해당한다.

 

 그러나 프로그램이 실제로 실행이 되어야만 사용되는 코드들은 컴파일 단계에서 검사할 수 없다. 프로그램이 실행중일 때 발생하는 에러는 프로그램의 안정성에 심각한 영향을 미치기 때문에 발생할 수 있는 오류에 대해 대비를 해주어야한다. 이는 런타임 에러(runtime error) 개념에 해당한다.

 

 JAVA에서는 이러한 에러들을 예외클래스(Class Exception)로서 처리한다. 하단의 이미지와 같이 예외처리를 위한 다양한 클래스를 제공하며, 모든 예외클래스의 최상위 클래스는 Exception 클래스로서 JAVA의 다른 모든 클래스와 마찬가지로 다형성 개념이 적용된다.

 

Class Exception의 구조

  

학습 과정에서 자주 만나는 Exception의 예

  • ArithmeticException : 정수를 0으로 나누는 경우 발생
  • ArraylndexOutOfBoundsException : 배열에서 인덱스 범위를 초과하는 경우 발생
  • NullpointerException : null값의 참조 변수로 객체접근연산자()를 사용하는 경우 발생
  • NumberFormatException : 문자열 데이터를 숫자로 변경하는 과정에서 발생

 상기한 Exception 클래스들은 모두 RuntimeException의 하위클래스로서, 여기에 해당하는 클래스들은 예외처리를 강제하지 않는 Unchecked Exception에 해당한다. RuntimeException의 하위클래스에 해당하지 않는 다른 모든 Exception클래스들은 예외처리를 필수적으로 요구하는 Checked Exception이다. 대표적으로는 입/출력 기능과 관련한 IOException을 들 수 있다.

 

▒ 예외 클래스(Class Exception)의 사용 방법

throws 키워드

 Checked Exception에 해당하는 메소드를 작성하다 보면 필연적으로 컴파일 에러가 발생하며 IDE는 예상되는 오류를 해결 할 것을 요구한다. 또한 예외처리를 강제하지 않는 메소드임에도 에러 발생이 예상되는 경우가 있을 수 있다. 이러한 경우에도 예외처리를 해주는 것이 바람직하다. 그러나 Exception이 필요한 모든 메소드에 대하여 일일히 예외처리를 해주는것은 매우 비효율적이다. 이 때 throws를 이용하면 해당 메소드를 실제로 구현하는 대상에게 예외처리의 책임을 미룰 수 있다.

 

public class TryCatch {
	
	//나누기 수행 메소드
	public void divide() {
		Scanner sc = new Scanner(System.in);
		
		int num1 = sc.nextInt();
		int num2 = sc.nextInt();
		
		System.out.printf("%d나누기 %d는 %d입니다. ",num1, num2, num1/num2);
	}
	
	//파일 입력 메소드
	public void fileInput(String fileName) {
		FileInputStream fis = new FileInputStream(fileName);
	}

}

예시의 첫 번째 메소드는 정수 num1num2를 입력받아 나누기를 수행하여 출력하는 divide()메소드이다. 이는 Unchecked Exception로서 예외처리를 필수적으로 요구하지 않지만 만약 사용자가 num20을 입력한다면 ArithmeticException에러가 발생할 것을 예상할 수 있다. 

 

두 번째 메소드는 매개변수로 파일명을 입력받아 FileInputStream객체를 생성하는 fileInput()메소드이다. 이는 Checked Exception로서 예외처리가 필수적으로 이루어져야 하기에 예시와 같이 코드를 작성하는 경우 컴파일러는 FileNotFoundException에러에 대한 처리를 요구하게된다.

 

이러한 에러들에 대응하는 방법은 크게 두 가지이다.

  • divide()메소드와 fileInput()메소드 내부에 try-catch문 생성
  • 이를 실제로 구현하는 대상(여기에서는 main메소드)에게 책임을 유예 

언뜻 보기에는 1번의 방법이 직관적이며 확실한 방법으로 보이나, 만약 에러가 발생 할 것으로 예상되는 메소드가 여러개 존재한다면? 그에 대해 일일히 예외처리 작업을 해주는 것은 효율적이지 못하다. 이 때 아래와 같이 throws를 사용하면 main메소드가 해당하는 종류의 에러들을 모아서 한 번에 처리하도록 유예 할 수 있는 것이다.

	//파일 입력 메소드
	public void fileInput(String fileName) throws FileNotFoundException {
	...중략
	}

 

try-catch문

 예외처리문의 구현은 try-catch문을 이용한다.

public class Main {
	public static void main(String[] args) {
	
		TryCatch tryCatch = new TryCatch();
		
		try {
        
			tryCatch.divide();
			tryCatch.fileInput("file.txt");
            
		} catch (Exception e) {
        
			System.out.println("에러가 발생했습니다.");
            
		}
		
}
}

 이는 위의 예시 클래스를 활용하여 main메소드에 작성한 가장 기본적인 try-catch문으로서, try{} 안에 에러가 발생할 것으로 예상되는 코드를 넣고, catch(Parameter p){} 안에는 에러 발생시 실행할 코드를 넣는 형태이다. 예시에서 발생할 수 있는 예외는 ArithmeticException, FileNotFoundException의 두 가지이나, Exception은 모든 예외클래스의 상위클래스이므로 catch (Exception e) {}와 같이 예외처리를 일원화할 수 있는 것이다.

 

그러나 발생할 수 있는 여러가지 예외를 모두 Exception클래스로 해결하는것은 복잡한 코드에는 대응하기 어렵기 때문에 아래와 같이 유형에 따라 catch문을 분리하여 작성함으로서 보다 적합한 예외처리를 구현할 수 있다.

		} catch (ArithmeticException e) {
			System.out.println("0으로 나눌 수 없습니다.");			
		} catch (FileNotFoundException  e) {	
			System.out.println("파일을 찾을 수 없습니다.");			
		}
        
        혹은
        
		} catch (ArithmeticException | FileNotFoundException e) {	
			System.out.println("에러가 발생했습니다.");
		}

 

finally와 try-with-resource문

		TryCatch tryCatch = new TryCatch();

			try(tryCatch){
				tryCatch.divide();
				tryCatch.fileInput("file.txt");
			} catch (Exception e) {
				System.out.println("오류가 발생했습니다.");
			} finally {
				System.out.println("finally블록은 항상 실행됩니다");
			}

추가적으로 finally블록을 사용할 수 있다. finally블록은 예외의 발생 여부와 무관하게 항상 실행된다. 활용예시로는 메소드 실행으로인해 사용된 리소스들을 닫아주는 코드를 넣는 경우를 들 수 있다.

 

try-with-resource문은 자바7부터 제공되는 기능으로서, 사용이 끝난 리소스들을 자동으로 닫아주는 기능을 한다.

try()안에 실행할 리소스가 들어가야하며, 실행할 리소스는 항상 AutoCloseable클래스를 implements 해야한다.

public class TryCatch implements AutoCloseable{

 

throw

 throw는 임의로 Exception을 발생시킨다. try-catch문을 다중으로 구현할 때 catch블록을 호출하는 수단으로 사용할 수 있다.

		try {
			throw new Exception();
		} catch (Exception e) {
			System.out.println("에러가 발생했습니다.");
		}