공대생의 개발 일기장

ch08 예외처리 본문

Java 공부

ch08 예외처리

SeoKyung 2023. 4. 11. 00:13

https://github.com/SeoKyung0221/Java-study

 

GitHub - SeoKyung0221/Java-study

Contribute to SeoKyung0221/Java-study development by creating an account on GitHub.

github.com

오늘도 한 발자국 자바를 공부하고 업로드한다. 사실 지금 내가 백엔드 파트를 맡고 있는 프로젝트를 하면서 느끼는건데 예외처리가 정말 시도때도 없이 나온다. 그렇기 때문에 공부를 급하게 해주었다.

 

먼저 이 구분에서부터 시작한다.

 

에러(Error) - 프로그램 코드에 의해서 수습될 수 없는 심각한 오류

예외(Exception) - 프로그램 코드에 의해서 수습될 수 있는 다소 미약한 오류

 

그렇기 때문에 우리는 우리가 처리할 수 있는 예외에 대해서 공부하는 것이다.

 

예외의 최고 조상은 Exception클래스이다. (물론 Exception의 조상은 Object클래스이다.) 그리고 이의 자손으로 RuntimeException, IOException이 있지만 보통은

 

1. Exception클래스와 그 자손들

2. RuntimeException클래스와 그 자손들

 

의 두 분류로 나뉘게 된다.

 

1. Exception클래스들은 주로 외부의 영향으로 발생하는 것으로 프로그램의 사용자들의 동작에 의해서 발생

ex) 존재하지 않는 파일의 이름을 적음, 실수로 클래스의 이름을 잘못 적음, 입력한 데이터 형식이 잘못됨.

 

2. RuntimeException클래스들은 프로그래머의 실수에 의해서 발생할 수 있는 예외들로 자바의 프로그래밍 요소와 관계가 깊다.

ex) 배열의 범위를 벗어남, 값이 null인 참조변수를 호출, 클래스간의 형변환 잘못, 정수를 0으로 나누는 행위

 

*컴파일러가 예외처리를 확인하지 않는 RuntimeException클래스들은 'unchecked예외', 예외처리를 확인하는 Exception클래스들은 'checked예외'라고 부른다. (unchecked예외는 예외처리를 하지 않았음에도 성공적으로 컴파일된다. 물론 정상적이진 않다. checked예외는 그냥 애초에 컴파일이 안되고 빨간줄이 그인다.)

 

이런 예외들을 처리해주는 것이 자주 보았던 try - catch문이다. 즉 예외처리라고 불리는 것이다.

 

예외처리는 그 정의와 목적을 확실하게 알아두어야 한다.

 

정의 - 프로그램 실행 시 발생할 수 있는 예외에 대비한 코드를 작성하는 것

목적 - 프로그램의 비정상 종료를 막고, 정상적인 실행상태를 유지하는 것

 

이때 발생한 예외를 처리하지 못하면 비정상적으로 프로그램이 종료되고 처리되지 못한 예외는 JVM의 예외처리기가 받아서 예외의 원을 화면에 출력한다.

 

try - catch문

 

try {
	// 예외가 발생할 가능성이 있는 문장들을 넣는다.
} catch(Exception1 e1){
	// Exception1의 예외가 발생했을 경우, 이를 처리하는 문장을 넣음
}

이와 같이 작동하며 e1은 예외의 참조변수이다. 여기서 한가지.

try - catch문 내에 try - catch문이 또 포함될 수 있다. catch블럭 괄호 내에서 선언된 참조변수 e1의 경우에는 지역변수와 같은 개념으로 catch 블럭 내에서만 유효한다. 다만, 내부에 또 들어있는 try - catch문에서는 같은 이름의 e1 참조변수를 사용해서는 안된다. 영역이 겹치기 때문에 구분해야 하기 때문이다.

 

try {

} catch(Exception e1){
	try {

	} catch(Exception e1) // 에러!
}

 

기본적으로 try - catch문은 예외가 발생하지 않았을 때는 catch문장은 작동하지 않는다. 그렇다면 예외가 발생했을 때는 try 문장 중간에 예외가 발생하자마자 거기서 try 블럭 내에서는 진행을 멈추고 이에 예외에 해당하는 catch문을 찾아 그쪽으로 이동해 이어서 수행한다. 만약 찾지 못한다면 예외를 처리하지 못한 것이므로 비정상적으로 프로그램이 종료된다.

 

앞서서 catch 괄호 ( )에는 처리하고자 하는 예외와 같은 타입의 참조변수 하나를 선언하는 것을 볼 수 있다. (e1) 이를 좀 더 자세히 살펴보면 예외가 발생하면 발생한 예외에 해당하는 클래스의 인스턴스가 만들어진다. 이 인스턴스가 각 catch 괄호 ( )내에 선언된 참조변수의 종류와 instanceof 연산자를 이용해서 검사를 하는 것이다. 이때 검사결과가 true라면 거기서 catch!

즉, Exception클래스는 모든 예외의 조상이므로 catch(Exception e)를 한다면 모든 예외는 여기서 처리된다.

 

어? 그렇다면 모든 예외는 그냥 알빠아니고 Exception클래스로 처리하면 되는거 아닌가? 너무 편한데?? 아니다. 한번 catch 블럭을 찾으면 그 다음 catch 블럭은 검사하지 않게된다. 즉, 어떤 종류의 예외가 발생하더라도 여기서 처리가 되는 것이다. 그렇기 때문에 여러 catch문이 있는 경우에는 Exception클래스의 catch문은 맨 뒤로 배치한다.

 

*JDK1.7부터 catch블럭을 '|'기호를 이용해서 합칠 수 있게 되었다. 그것도 개수 제한없이! 이를 멀티  catch블럭이라고 한다. 참고로 | 는 or연산자가 아니다. 그냥 OR 기호이다. 주의할 점은 | 기호로 연결된 예외 클래스는 조상과 자손관계여서는 안된다. 또, 이 여러 예외 클래스에 의해서 선언된 참조변수 e는 이 예외 클래스들의 공통 분모인 조상 예외 클래스에 선언된 멤버만 사용가능하다.마지막으로 멀티 catch블럭에 선언된 참조변수 e는 상수로 값을 변경할 수 없다.

 

 

이제부터 예외에 관련된 메서드를 살펴보자

printStackTrace( ) - 예외발생 당시의 호출스택(Call Stack)에 있었던 메서드의 정보와 예외 메시지를 화면에 출력한다.

getMessage( ) - 발생한 예외클래스의 인스턴스에 저장된 메시지를 얻을 수 있다.

 

이를 이용해서 어떤 예외가 발생했는지 또 호출스택을 살피면서 어디에서 예외가 발생했는지 확인할 수 있다.

 

고의로 예외를 발생시킬 수도 있다. 바로 키워드 throw다. 사용하기 위해서는 약간의 준비가 필요하다.

 

1. 먼저, 연산자 new를 이용해서 발생시키려는 예외 클래스의 객체를 만든다.2. 키워드 throw를 이용해서 예외를 발생시킨다.

Exception e = new Exception("고의로 발생시켰음");

throw e;

이때, "고의로 발생시켰음"이 Exception인스턴스에 메시지로 저장되어 getMessage( )를 통해 얻을 수 있다.

 

 

메서드에 예외를 선언하는 방법도 있다. 이때의 키워드는 throws이다. 예외가 여러개일 경우에는 쉼표 , 로 구분한다.이것이 의미하는건 메서드 내에서 ~~예외가 발생할 수 있다고 알려주는 것이다. 즉, 가능성이 있다! 라고 하는 것이다.

 

*참고로 앞서 상속에서 배웠을 때 오버라이딩의 예외에 대한 주의점을 다시 한번 살펴보자. 추가적으로 조상 예외의 경우에는 그 자손타입의 예외도 개수로 카운트된다. 즉, 상속관계까지 고려해야 한다.

 

그렇다면 왜? throws로 알려주는 것일까? 그냥... 예외가 발생한다는걸 알기 쉽게 하는 것일까? 물론 그런 것도 있찌만 중요한 것은 자신(throws의 메서드)을 호출한 메서드에게 자신의 예외를 전달하여 예외처리를 떠맡기는 것이다. 한마디로 날 좀 도와달라는 표식인 셈이다.

 

 

try - catch의 finally 블럭이라는 것도 있다. 이름 그대로 try, catch의 블럭이 모두 등장하고 마지막에 등장하는 블럭이다.

try{

} catch {

} finally {
	// 예외의 발생여부 관계없이 항상 수행되어야 하는 문장들을 넣는다.
}

finally는 예외가 있든 없든 항상 수행되는 것이다. 즉, try에서 catch문으로 넘어가서 예외처리를 하고 다시 나오든 말든 finally 블럭으로 들어가고, 예외가 없으면 try가 끝나면 finally로 들어오는 것이다. 여기서 중요한 점은 어떤 메서드에서 try - catch - finally가 사용된다고 했을 때 try에서 return이 수행되어도 그래도 finally블럭이 먼저 실행된 후에 메서드가 종료된다!

 

사용자 정의의 예외를 만들 수도 있다.

 

class UnsupportedFuctionException extends RuntimeException {
	private final int ERR_CODE;
		
	UnsupportedFuctionException(String msg, int errCode) {
		super(msg);
		ERR_CODE = errCode;
	}
		
	UnsupportedFuctionException(String msg) {
		this(msg, 100);
	}
	
	public int getErrCode() {
		return ERR_CODE;
	}
		
	public String getMessage() {
		return "["+getErrCode()+"]"+super.getMessage();
	}
}

class Example{
	public static void main(String[] args) throws Exception
		{
			throw new UnsupportedFuctionException("지원하지 않는 기능입니다.", 100);
		}
	}	
}

하나의 예시지만 말그대로 새로운 사용자 정의 예외를 만드는 것이다. 여기서 ERR_CODE는 100으로 생성자에서 기본값으로 초기화된다. msg라는 것을 통해 사용자정의 예외 클래스도 메시지를 저장할 수 있게된다. 생성자를 통해서 말이다. 참고로 getMessgae의 경우 조상클래스의 메서드를 오버로딩한 것이다.

 

 

*자동 자원 반환 try - with - resources문은 책으로 보길 바란다.

'Java 공부' 카테고리의 다른 글

Java 현황  (0) 2023.09.04
ch07 객체지향프로그래밍2  (0) 2023.03.23
ch06 객체지향프로그래밍1  (0) 2023.03.12
ch05 배열  (0) 2023.02.26
ch04 조건문과 반복문  (1) 2023.02.19