📕 예외 처리(exception handling)란?

예외 처리(exception handling) 이란, 프로그램 실행 시 발생할 수 있는 예기치 못한 예외의 발생에 대비한 코드를 작성하는것이다.

자바의 코드를 예외 처리를 한다고 해서 프로그램의 예외 상황 자체를 막을 수는 없다.

"예외"는 프로그래머가 직접 예측하여 막을 수 있는 처리가능한 오류
즉, 예측하여 막을 수 있는 오류를 회피하는 식으로 처리하여 프로그램이 정상적인 실행상태를 유지할 수 있도록 함




📖 1. 프로그램 오류

📝 1-1. 발생시점에 따른 에러

  • 컴파일 에러 (compile error)
    : 컴파일 시에 발생하는 에러

컴파일 시점에서 발생하는 에러로 소스코드를 컴파일러가 컴파일하는 시점에서 소스의 오타나 잘못된 구문, 자료형 체크등 검사를 수행하는데 여기서 발생하는 에러를 컴파일 에러라 하며 이 시점에서 발생하는 문제들을 수정 후 컴파일을 성공적으로 마칠경우 클래스 파일(*.class) 파일이 생성된다.

  • 런타임 에러 (runtime error)
    : 실행 시에 발생하는 에러

프로그램 실행 시점에서 발생하는 에러로 컴파일러는 컴파일 시점에서 문법 오류나 오타같은 컴파일시점에서 예측가능한 오류는 잡아줄 수 있지만, 실행 중 발생할 수 있는 잠재적인 에러까지 잡을 순 없다.
그래서 컴파일은 문제없이 완료되어 프로그램 실행이되고 실행도중 의도치않은 동작에 대처하지못해 에러가 발생할 수 있다.

  • 논리적 에러 (logical error)
    : 실행은 되지만 의도와 다르게 동작하는 것

소스 코드 컴파일도 정상적으로 되고 런타임상 에러가 발생하는 것도아닌 개발자의 의도와는 다르게동작하는 에러를 뜻한다. 버튼을 클릭하면 팝업이 뜨게 만들었으나 팝업이아닌 새로운 페이지가 뜨거나 아무동작을 안하거나 하는 것처럼 시스템상 프로그램이 멈추거나 하지는 않지만, 의도와는 다르게 동작하는 것을 말한다.

📝 1-2. 에러(Error)와 예외(Exception)

런타임 시점에서 발생하는 오류로 에러(error)와 예외(exception)으로 나뉜다.


  • 에러(Error) : 메모리 부족(OutOfMemoryError)이나 스택오버플로우(StackOverflowError)와 같이 일단 발생하면 복구할 수 없는 심각한 오류
  • 예외(exception): 코드에 의해 수습될 수 있는 다소 미약한 오류

📖 2. 예외클래스의 계층 구조

Checked ExceptionUnchecked Exception
클래스Exception 클래스의 자손들 중 Runtime Exception을 제외한 모든 클래스Runtime Exception 클래스와 자손 클래스
처리 여부반드시 예외처리 해야함명시적 처리 강제하지 않음
발생 시점컴파일 단계실행 단계
예외 발생 시 트랜잭션 처리roll back 안 함roll back
대표 예외IOException, SqlExceptionNullPointerException, IllegalArgumentException, IndexOutOfBoundException, SystemException

모든 예외는 Exception 클래스를 상속 받는다!


📖 3. 예외 처리 방법

📝 3-1 예외 처리 구문 (try ~ catch)

try {
	<수행할 문장1>; // 문장1을 수행한다
    <수행할 문장2>; // 문장2을 수행한다
    ...
} catch(예외1) {
	<수행할 문장A>; // 예외1이 발생할 경우 문장A 수행
    ...
} catch(예외2) {
	<수행할 문장a>; // 예외2가 발생할 경우 문장a 수행
	...
}

catch블록을 작성할 때 예외1은 예외2보다 하위 클래스 여야 함!
ex) 예외1 : NullPointerException / 예외2 : Exception


🔴 예외 처리 해보기1

package java_studying_exception;

public class ExceptionSample {
	public static void main(String[] args) {
		int c;
		try {
			c = 4/0; // 0으로 나누어라
			System.out.println(c);
		} catch (ArithmeticException e) { // ArithmeticException일 경우 c는 -1
			c = -1;
			System.out.println(c);
		}
	}
}

🔵 결과 값

-1

try내 연산이 실행 되었고, 예외가 발생하였으므로 -1이 출력됨.


📝 3-2 예외 처리 하기 (finally 구문)

여기서는 예외가 발생하건 발생하지 않건 공통으로 수행되어야할 코드가 쓰여진다. 임시 파일의 삭제 등 뒷정리 코드가 쓰인다.

try {
	<수행할 문장1>; // 문장1을 수행한다
    <수행할 문장2>; // 문장2을 수행한다
    ...
} catch(예외1) {
	<수행할 문장A>; // 예외1이 발생할 경우 문장A 수행
    ...
} catch(예외2) {
	<수행할 문장a>; // 예외2가 발생할 경우 문장a 수행
	...
} finally {
	<수행할 문장B>; // 반드시 실행됨
}

🔴 예외 처리 해보기2

package extra;

public class ExceptionSample {

	public static void main(String[] args) {
		try {
			int[] array = new int[] {1,2,3,4,5};
			
			for(int i=0; i<10; i++) {
				System.out.println(array[i]);
			}
			
		}catch(ArrayIndexOutOfBoundsException | NullPointerException e) { // "|" 를 이용
			System.out.println("exception!");
		}finally {
			System.out.println("실행 완료!"); // 반드시 실행 됨
		}
	}
}

🔵 결과 값

1
2
3
4
5
exception!
실행 완료! // finally 구문으로 반드시 실행 됨

"|" 기호를 이용해 여러 catch 블럭을 합칠 수 있다.


💬 Exception 클래스 활용

클래스의 상속 관계(다형성)를 이용하여 예외클래스의 상위 클래스인 Exception 클래스 타입을 catch문 아규먼트에 선언하면, 코드 몇줄만으로 자바의 나머지 모든 예외 클래스를 catch 문으로 받아들일 수 있게 된다. 다만, 세세하게 어떠어떠한 예외인지는 부모 클래스인 Exception 클래스만으로는 알수 없게 된다는 단점이 있다.


📝 3-3 예외 메세지 출력

오류와 예외 모두 자바의 최상위 클래스인 Object를 상속받는다.
그리고 그 사이에는 Throwable 클래스와 상속관계가 있는데, Throwable 클래스의 역할은 오류나 예외에 대한 메시지를 담는 것이다. 대표적으로 getMessage() 와 printStackTrace() 메서드가 바로 이 클래스에 속해 있다.
당연히 Throwable을 상속받은 Error와 Exception 클래스에서도 위 두 메서드를 사용할수 있게 된다.


  • printStackTrace() : 예외발생 당시의 호출스택(Call Stack)에 있었던 메서드의 정보와 예외 메시지를 화면에 출력
  • getMessage() : 발생한 예외클래스의 인스턴스에 저장된 메시지를 얻을 수 있다.

🔴 예외 처리 해보기3

package extra;

public class ExceptionSample {

	public static void main(String[] args) {
		try {
			int[] array = new int[] {1,2,3,4,5};
			
			for(int i=0; i<10; i++) {
				System.out.println(array[i]);
			}
			
		}catch(ArrayIndexOutOfBoundsException | NullPointerException e) {
			System.out.println(e.getMessage()); // getMessage() 메서드 실행
			e.printStackTrace(); // printStackTrace() 메서드 실행
		}finally {
			System.out.println("실행 완료!");
		}
	}
}

🔵 결과 값

1
2
3
4
5
Index 5 out of bounds for length 5 // getMessage() 메서드 
java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 5
	at extra.ExceptionSample.main(ExceptionSample.java:10) // printStackTrace() 메서드 
실행 완료!

📖 4. 예외 발생시키기(throw)

개발자가 의도적으로 예외를 발생시키는 것 역시 가능하다.
throw라는 키워드를 사용하면 되는데, 주로 사용자가 구현하는 비즈니스 로직에서 컴파일의 문법 오류는 없지만 로직 자체가 개발자가 의도한대로 진행되는지에 대한 의 검증(Validation)로직을 통과하지 못했을 경우에 고의로 예외를 발생시켜야 할 때 사용한다.

Exception e = new Exception("고의로 예외 발생시킴");
throw e;

throw new Exception("고의로 예외 발생시킴");

🔴 예외 처리 해보기4

package extra;

public class ExceptionSample {

	public static void main(String[] args) {
		try {
			Exception e = new Exception("고의로 예외 발생시킴"); // 에러메세지 안에 저장됨
			throw e;
		} catch(Exception error) {
			error.printStackTrace(); // 상세한 에러
			System.out.println("예외 메세지: "+error.getMessage()); // 에러메세지
		} finally {
			System.out.println("실행 완료!");
		}
	}
}

🔵 결과 값

java.lang.Exception: 고의로 예외 발생시킴
	at extra.ExceptionSample.main(ExceptionSample.java:7) // printStackTrace() 메소드
예외 메세지: 고의로 예외 발생시킴 // getMessage() 메소드
실행 완료! // finally 구문

예외의 생성자 파라미터값은 .getMessage()메소드로 출력 가능하다.




📖 5. 예외 떠넘기기(throws)

예외가 발생할 수 있는 코드를 작성할 때 try - catch 블록으로 처리하는 것이 기본이지만, 경우에 따라서는 다른 곳에서 예외를 처리하도록 호출한 곳으로 예외를 떠넘길 수도 있다.
이때 사용하는 키워드가 throws이다. throws 는 메소드 선언부 끝에 작성되어 메소드에서 예외를 직접 처리(catch)하지 않은 예외를 호출한 곳으로 떠넘기는 역할을 한다.

🔴 예외 처리 해보기5

package extra;

public class ExceptionSample {

	public static void main(String[] args) {
		exception1();
		exception2(); 
	}

	private static void exception1() {
		
		try {
			NullPointerException exception1 = new NullPointerException("고의로 예외 발생시킴01"); // exception1이라는 NullPointerException 타입 객체에 메세지 저장
			throw exception1;
		} catch(NullPointerException error) {
			System.out.println("예외 메세지: "+error.getMessage()); // 에러메세지
		}
	}

	private static void exception2() {
		try {
			Exception exception2 = new Exception("고의로 예외 발생시킴02"); // exception2이라는 에러 객체에 메세지 저장
			throw exception2;
		} catch(Exception exception2) {
			System.out.println("예외 메세지: "+exception2.getMessage()); // 에러메세지
		}
	}
}

🔵 결과 값

예외 메세지: 고의로 예외 발생시킴01 // NullPointerException
예외 메세지: 고의로 예외 발생시킴02 // Exception

🔴 예외 처리 해보기6 (throws 사용)

public class ExceptionSample {
	public static void main(String[] args) {
		try{
			exception1();
			exception2();
		} catch (NullPointerException childerror) {
			System.out.println("예외 메세지: "+childerror.getMessage());
		} catch (Exception parenterror) {
			System.out.println("예외 메세지: "+parenterror.getMessage());
		}
	}

	private static void exception1() throws NullPointerException{
		throw new NullPointerException("고의로 예외 발생시킴01"); // exception1이라는 에러 객체에 메세지 저장
	}

	
	private static void exception2() throws Exception{
		throw new Exception("고의로 예외 발생시킴02"); // exception2이라는 에러 객체에 메세지 저장
	}
}

🔵 결과 값

예외 메세지: 고의로 예외 발생시킴01

throws 키워드가 붙어 있는 메소드는 반드시 try 블록 내에서 호출되어야 한다




📖 6. 예외 발생과 코드의 트랜잭션

각 메서드에서 일일히 try - catch 하면, 메인 메소드에 있는 메서드 실행 코드 부분은 3개 모두 실행 자체는 된다.

왜냐하면 예외처리를 각 메서드에서 하기 때문에 상위의 메인 메서드의 코드들은 모두 실행되게 된다.


반면에 throws를 통해 예외처리를 상위 메서드에서 모아 처리를 한다면, 코드 어느 한곳에서 예외가 발생하면 그 뒤의 나머지 코드들은 당연히 실행되지 않게 된다

이처럼 try - catch 문은 어디에 사용하냐 어디서 throws 하느냐에 따라 자바 코드의 작업 단위(트랜잭션)가 완전히 달라질 수 있게 되는 것이다.




📖 7. 연결된 예외(Chained Exception)

우리가 클래스를 상속하여 다형성을 이용하여 부모 클래스 타입으로 다뤄온 것 처럼, 예외도 마치 부모 예외로 감싸서 보내 마치

예외의 다형성 처럼 다룰 수 있다.

예를 들어 예외 A가 발생했다면 이를 예외 B로 감싸서 throw하는 식으로, 마치 예외를 다른 예외로 감싸서 던진다고 보면된다. 그래서

예외 A가 예외 B를 발생시켰다면, A를 B의 '원인 예외(cause exception)'라고 한다.

Exception 클래스가 상속하고 있는 Throwable 클래스에는 chained exception을 가능하게 해주는 다음 메서드를 지원한다.


  • 예외객체B.initCause(예외객체A) : 예외객체A를 예외객체B의 원인 예외로 등록
  • 예외객체B.getCause() : 예외객체B의 원인 예외를 반환

🔴 예외 처리 해보기7

class InstallException extends Exception{
	// 사용자 정의 예외 : 설치예외
	private String msg;
	
	public InstallException(String string) {
		this.msg = string;
	}
	public String getMessage() {
		return this.msg;
	}
}

class SpaceException extends Exception {
	private String msg;
	// 사용자 정의 예외 : 공간 예외
	public SpaceException(String string) {
		this.msg = string;
	}
	public String getMessage() {
		return this.msg;
	}
}

public class ExceptionSample {

	public static void main(String[] args) {
		try {
			install();
		} catch(InstallException e) {
			System.out.println("원인 예외 : " + e.getCause());
			System.out.println("감싼 예외 : " + e.getMessage());
		}
	}

	private static void install() throws InstallException{
		try {
			throw new SpaceException("설치할 공간이 부족합니다.");
		} catch(SpaceException e) {
			InstallException ie = new InstallException("설치중 예외발생"); 
			// InstallException타입의 ie 예외 객체 생성
			ie.initCause(e);
			// SpaceException타입의 e 예외 객체를  InstallException타입의 ie 예외 객체의 원인 예외로 등록
			throw ie;
			// ie 예외 발생
		} 
	}
}

🔵 결과 값

원인 예외 : java_studying_exception.SpaceException: 설치할 공간이 부족합니다.
감싼 예외 : 설치중 예외발생


연결된 예외(chained exception)를 사용하는 또 다른 이유는 checked예외를 unchecked예외로 바꿀 수 있도록 하기 위함이다.


🔴 예외 처리 해보기8

class InstallException extends Exception{
	// 사용자 정의 예외 : 설치예외
	private String msg;
	
	public InstallException(String string) {
		this.msg = string;
	}
	public String getMessage() {
		return this.msg;
	}
}

class SpaceException extends Exception {
	private String msg;
	// 사용자 정의 예외 : 공간 예외
	public SpaceException(String string) {
		this.msg = string;
	}
	public String getMessage() {
		return this.msg;
	}
}

public class ExceptionSample {

	public static void main(String[] args) {
			install();
	}

	private static void install(){
		try {
			throw new InstallException("설치할 공간이 부족합니다.");
		} catch(InstallException e) {
			RuntimeException ex = new RuntimeException(); // unchecked 예외
			ex.initCause(e); // checked예외[e]를 unchecked예외[ex]로 감싸 줌
		} finally {
			System.out.println("수행 완료");
		}
	}
}

🔵 결과 값

수행 완료

0개의 댓글

Powered by GraphCDN, the GraphQL CDN