네트워크 캠퍼스/JAVA

240117 자바의 예외처리(Exception, Runtime Exception, try~catch, 다중 catch, throws)

gayeon_ 2024. 1. 17. 15:55

자바의 예외처리

예외 처리(Exception)

  • 에러에는 심각한 에러(serious error)와 가벼운 에러(mild error)가 있다. 심각한 에러는 시스템 상의 문제로 인한 에러로 개발자가 처리할 수 없는 에러를 말한다. 가벼운 에러는 개발자가 코드를 통해 처리할 수 있는 에러를 말하며 이 방식을 예외처리라고 한다.
  • 예외에는 컴파일러 체크 예외(Checked Exception)와 실행 예외(Runtime Exception)가 있다.
  • 컴파일러 체크 예외는 자바 소스를 컴파일하는 과정에서 예외 처리 코드를 검사하여 예외 처리 코드가 없다면 컴파일 오류가 발생한다.
  • 실행 예외는 컴파일하는 과정에서 예외처리 코드를 검사하지 않는 예외이다.
  • 예외 처리는 컴파일 예외와 실행 예외에 대한 대처방법이다.
  • 예외 처리는 시스템 스스로 오류를 복구하는 것이 아니고 오류 발생 가능성이 있는 부분에 대한 처리를 미리 프로그래밍 해주는 것이다.
  • 프로그램에서 문제가 될만한 부분을 예상하여 사전에 "문제가 발생하면 이렇게 처리하라" 라고 프로그래밍 하는 것을 예외 처리라고 한다.
  • 예외 발생시에는 System.out.println(); 대신 System.err.println();으로 메세지를 띄우는게 좋다.

 

 

실행 예외(Runtime Exception)

  • 실행 예외는 컴파일러가 예외 처리 코드를 체크하지 않기 때문에 오로지 개발자의 경험에 의해서 예외 처리 코드를 삽입해야 한다.
  • 만약 개발자가 실행 예외에 대해 예외처리 코드를 넣지 않았을 경우 해당 예외가 발생하면 프로그램은 곧바로 종료된다.

 

주요 실행 예외

NullPointerException

객체 참조가 없는 상태, 즉 null 값을 갖는 참조 변수로 객체 접근 연산자인 dot(.)를 사용했을 때 발생한다.

 

ArrayIndexOutOfBoundsException

배열에서 인덱스 범위를 초과하여 사용할 경우 발생한다.

 

NumberFormatException

문자열로 되어 있는 데이터를 숫자로 변경하는 경우에 발생한다.

 

ClassCastException

형 변환은 부모 클래스와 자식 클래스간에 발생하고 구현 클래스와 인터페이스 간에도 발생한다.  

이러한 관계가 아니라면 다른 클래스로 타입을 변환할 수 없다.

상속관계나 인터페이스 관계가 없는 클래스들을 억지로 형 변환 할 경우 발생한다.

 

 

NullPointerException 예제

package exception.runtime;

public class NullPointerExample {

	public static void main(String[] args) {
		String str = null;
		// str = "HELLO";
		
		// toLowercase()는 모든 문자를 소문자로 만들어준다.
		System.out.println(str.toLowerCase());

	}

}

 

 

ArrayIndexOutOfBoundsException 예제

package exception.runtime;

public class ArrayIndexExample {

	public static void main(String[] args) {

		int[] arr = {3, 6, 9};
		
		System.out.println(arr[5]);
	}

}

 

 

NumberFormatException 예제

package exception.runtime;

public class NumberFormatExample {

	public static void main(String[] args) {
		String a = "34";
		String b = "21";
		System.out.println(a + b);
		
		// str -> int 변환
		int i = Integer.parseInt(a); // 문자를 숫자로 변환
		int j = Integer.parseInt(b); // 문자를 숫자로 변환
		System.out.println(i + j);
		
		// parseInt는 문자열 내부에 순수한 정수가 들어있어야 변환하며 
		// 정수값이 아니라면 NumberFormatExeption이 발생한다.
		String str = "Hello";
		System.out.println(Integer.parseInt(str));
		

	}

}

 

 

ClassCastException 예제

package exception.runtime;

// 하나의 클래스파일에 2개 이상의 클래스 선언 가능(자주 사용하지는 않는다.)
// 상속관계: 부모 Animal을 상속한 자식 Dog, Cat
class Animal{}
class Dog extends Animal{}
class Cat extends Animal{}

public class ClassCastExample {
	public static void main(String[] args) {
		Dog d = new Dog();
		Animal da = d;
		d = (Dog)da;
		
		System.out.println("타입 변환 성공: Animal -> Dog");
		
		Animal c = new Cat();
		Dog d2 = (Dog)c;
		System.out.println("타입 변환 성공?: Dog -> Cat");
	}
}

 

 

try~ catch

  • 프로그램에서 예외가 발생했을 경우 프로그램의 갑작스러운 종료를 막고, 정상 실행을 유지할 수 있도록 처리하는 코드를 예외 처리 코드라고 한다.
  • try~ catch ~ finally 블록은 생성자 내부나 메서드 내부에서 작성되어 컴파일 예외와 실행 예외가 발생할 경우에 예외 처리를 할 수 있게 한다.
  • try 블록에는 예외 발생 가능성이 있는 코드를 작성한다. try 블록의 코드가 예외 발생 없이 정상 실행되면 catch 블록은 실행되지 않는다.
  • try 내부에서 예외가 발생하면 즉시 실행을 멈추고 catch 블록으로 이동하여 예외 처리 코드를 실행한다.
  • 예외 발생 여부와 상관없이 항상 실행할 내용이 있다면 finally 블록 내부에 실행 내용을 작성한다.

 

finally 구문이 실행되지 않는 경우

  1. finally 구문 이전에 System.exit() 구문을 호출했을 시
  2. 컴퓨터가 꺼져서 시스템이 멈추었을 시
  3. finally 블록 내부에서 예외가 발생했을 시

 

try~catch문을 사용하지 않았을 때 제어문으로 결과를 제어하는 예제

package exception.trycatch;

// 예외문이 없었을 때는 제어문으로 결과 제어
// 문제점: 가독성이 좋지 않다.
public class NoTryCatchExample {
	public static void main(String[] args) {
		int preValue = 5;
		int nextValue = 2;
		
		// nextValue에 0만 안 들어오면 예외 발생 없음
		if(nextValue != 0) {
			System.out.println(preValue / nextValue);
		} else {
			System.out.println("0을 나누는 값으로 넣을 수 없습니다.");
		}
	}
}

 

 

try~catch문을 사용해 예외를 처리한 예제

package exception.trycatch;

public class TryCatchExample1 {
	public static void main(String[] args) {
		int i = 10;
		int j = 0;
		// int j = 5;
		
		try { // 예외가 발생할 수 있는 구문 기입
			System.out.println(i / j); // 예외 발생 가능성 존재
			System.out.println("예외 발생하지 않을 때만 실행됨");
			
		} catch(Exception e) { // catch 블럭에는 Exception의 종류를 기입
			System.out.println("0으로 나눠서 catch 블럭으로 넘어왔습니다.");
			
		} finally { // try, catch 둘 중 어느블럭이라도 실행되면 마무리 블럭 실행
			System.out.println("어쨌든 잘 마무리 했습니다.");
		}
		System.out.println("프로그램 종료!");
	}
}

 

 

try~catch문을 사용해 예외를 처리한 예제2

package exception.trycatch;

public class TryCatchExample2 {

	public static void main(String[] args) {
		// 어떤 종류가 되었건 직접 예외가 발생할 수 있는 구문을 작성한 다음
		// try~catch를 이용해 예외 발생시 처리되도록 프로그래밍하기
		// 0으로 나누기를 제외한 나머지를 이용하기
		String str = "JAVA";
		int integer = 10;
		
		try {
			System.out.println(Integer.parseInt(str) + 10);
		} catch(Exception e){
			System.out.println("알파벳은 정수로 변환할 수 없습니다.");
		} finally {
			System.out.println("예외문 종료");
		}
		System.out.println("프로그램 종료");
	}

}

 

 

다중 catch

  • try 블록 내부는 다양한 종류의 예외가 발생할 수 있다. 예외가 여러 가지 발생한다면 다중 catch 블록을 작성하여 예외들을 처리한다.
  • 다중 catch 블록을 작성할 때 주의할 점은 상위 예외 클래스가 하위 예외 클래스보다 아래쪽에 위치해야 한다.
  • catch 블록은 위에서부터 차례대로 검색되므로 상위 예외 클래스의 catch 블록이 위에 있다면 하위 예외 클래스의 catch블록은 실행되지 않는다.
  • 자바 7 버전부터 하나의 catch 블록에서 여러 개의 예외를 처리할 수 있도록 기능이 추가되었다.
  • catch() 괄호 안에 동일하게 처리하고 싶은 예외를 | 로 연결하면 된다. 이 방식을 사용할 때는 두 예외가 상속 관계가 있으면 안 된다.

 

다중 catch 예제

package exception.muti;

public class MultiCatchExample {

	public static void main(String[] args) {
		String data1 = "30";
		String data2 = "11";
		
		try {
		// NumberFormatException 발생 가능
		int i = Integer.parseInt(data1);
		int j = Integer.parseInt(data2);
		
		// ArithmeticException 발생 가능
		int result = i / j;
		System.out.println("i / j = " + result);
		
		// NullPointerException 발생 가능
		String str = null;
		str.charAt(0); // 0번째 문자 얻기인데 null
		} catch (NumberFormatException | NullPointerException e) {
			System.err.println("데이터를 숫자만 넣어주세요.");
			System.err.println("혹은 문자를 제대로 만들어주세요.");
		} catch(ArithmeticException e) {
			System.err.println("0으로 나눌 수 없습니다.");
		} catch(Exception e) { // 범용 에러 처리 (대부분의 에러처리)
			System.err.println("알 수 없는 에러가 발생했습니다.");
			System.err.println("복구 중입니다.");
		}

	}

}

 

 

throws

  • try ~catch 구문이 예외가 발생했을 때 직접 해결을 하고자 하는 코드라면 throws는 메서드나 생성자를 호출한 곳으로 예외를 떠넘기는 코드이다.
  • 즉 예외처리를 직접 수행하지 않고 메서드 호출자에게 예외를 던지는 방법이다.
  • throws 키워드가 붙어있는 메서드는 반드시 try 블록 내부에서 호출되어야 한다. 그리고 catch블록에서 떠넘겨 받은 예외를 처리해야 한다.
  • main() 메서드에서 throws를 사용하는 것은 예외처리를 JVM에게 넘기겠다는 의미이다. 그러나 JVM은 그 예외를 직접 처리해주지 않고 예외가 발생하면 예외 메시지만 출력하고 프로그램을 종료시킨다.

 

throws 예제

package exception.throws_;

public class ThrowsExample {

	public static String[] greetings = {"안녕", "싸웠다", "헬로"};
	
	/*
	 * 예외의 원인이 메서드 선언부가 아닌 호출부에 있을 경우
	 * 메모리 영역이 다르므로 예외처리를 메서드 호출지역으로 떠넘겨줘야 한다.
	 * 이를 throws라고 하고, 메서드 혹은 생성자 호출 시 예외처리를 강요할 때 사용한다.
	 */
	
	// throws 오른쪽 종류 예외가 터지면 호출부(이 예제에서는 main)에게 처리를 떠넘기게 된다.
	public static void greet(int idx) throws Exception {
		// 메서드 안에 try~catch문을 사용할 수는 있지만 너무 복잡하고
		// 객체지향적이지 않다.
		// try {
			System.out.println(greetings[idx]);
		// } catch(ArrayIndexOutOfBoundsException e) {
			
		// }
	}
	
	public static void main(String[] args) {
		// throws가 붙어있는 메서드나 생성자 호출 시에는
		// 해당 메서드를 try 블록 내부에서 호출해야 예외처리를 진행해준다.
		// greet(3); // 3이 없는데 3 호출 
		// throws를 작성하니 greet(3);에 밑줄 발생 -> try~catch문 작성 필요
		
		try {
			greet(3);
		} catch(Exception e) {
			// .printStackTrace()는 예외발생 경로를 추적하는 메시지를 출력한다.
			// 주로 개발 과정에서 예외의 원인을 역추적할 때 유용하다.
			e.printStackTrace();
		}
		System.out.println("프로그램 정상 종료");
	}

}

 

예외가 발생해서 프로그램이 비정상 종료가 된 것이 아닌

위 출력된 예외문구는 .printStackTrace()로 예외발생 경로가 출력된 것이다.

경로 출력 후 프로그램이 정상 종료되었다.