JAVA의 정석 | Chapter 8 예외처리

Yunny.Log ·2022년 6월 22일
0

JAVA

목록 보기
17/29
post-thumbnail

1. 예외처리

1.1 에러

1) 컴파일 에러 :

  • 컴파일 시 발생 에러 (소스코드 오타, 잘못된 구문, 자료형 체크)
  • 아직 RUN 안됐을 때 발생

2) 런타임 에러 :

  • 컴파일 잘 되었다고 프로그램 실행 시에도 에러 발생하지 x 아니다 / 프로그램 실행 도중에 발생하는 것!
  • RUN 된 후 발생
  • 컴파일러가 소스코드의 기본 사항은 컴파일 시 모두 걸러주기는 가능하지만, 실행 중 발생할 잠재적 오류까진 검사 불가능이기 때문!
  • 자바는 실행 시 발생가능한 프로그램 오류를 예외에러로 구분
    1) 에러 : 발생하면 복구 불가능한 심각 오류
    2) 예외 : 발생해도 수습 가능한 덜 심각한 것

  • 에러 발생 시 프로그램의 비정상적 종료 막을 길 존재 x

  • 예외는 프로그래머가 미리 적절 코드 입력 -> 비정상적 종료 방지 가능

1.2 예외 클래스 계층 구조

RuntimeException - 개발자의 실수

  • RuntimeException 클래스들은 주로 프로그래머 실수에 의해 발생
    (ex) 인덱스 에러, nullPointerException, 형변환 잘못 등 ,,

Exception - 사용자의 실수 (외적 요인)

  • Exception 클래스들은 주로 외부의 영향으로 발생할 수 있는 것,
  • 사용자의 동작에 의해 발생 경우 多
    (ex) 존재하지 않는 파일 이름 입력, 입력 데이터 형식 잘못 에러

1.3 예외 처리

  • 예외처리 : 프로그램 실행 시 발생 가능한 예기치 못한 예외 발생 대비 코드 작성

  • 정상적 실행상태 유지 가능하게 하는 것

  • 원래 예외가 발생하면 비정상적으로 프로그램이 종료되고 만다.

  • 따라서 해당 예외 발생 -> catch해서 적절한 대응, 즉 처리를 해줘야 한다.

  • 하나의 try 블럭 다음에 여러 종류의 예외 처리 가능하도록 하나 이상의 catch 블럭 오는 것 가능,

class ExceptionEx1 {
	public static void main(String[] args) 
   {
		try  {
			try	{	} catch (Exception e)	{ }
		} catch (Exception e)	{
			try	{	} catch (Exception e) { }	// 에러. 변수 e가 중복 선언되었다.
		} // try-catch의 끝

		try  {

		} catch (Exception e)	{

		} // try-catch의 끝
	}	// main메서드의 끝
}
  • catch 블럭 혹은 try 블럭 안에 또 다른 try catch 문 들어있기 가능
  • 그런데 catch 블럭 내에 또 하나의 try - catch가 포함된다면 같은 이름의 참조변수 사용하면 안됨 => 두 참조변수 영역이 겹쳐서 다른 이름 사용해야만 구분 가능

1.4 try catch 의 흐름

class ExceptionEx4 {
	public static void main(String args[]) {
			System.out.println(1);			
			System.out.println(2);
			try {
				System.out.println(3);
				System.out.println(4);
			} catch (Exception e)	{
				System.out.println(5); // 실행 X 
			} //try catch 끝 
			System.out.println(6);
	}	// main메서드의 끝
}

try 블럭에서 예외가 발생하지 않는 경우엔 catch 블럭 거치지 않고, try catch 문 빠져나가서 수행 계속 진행

class ExceptionEx5 {
	public static void main(String args[]) {
			System.out.println(1);			
			System.out.println(2);
			try {
				System.out.println(3);
				System.out.println(0/0);	
				System.out.println(4); 	// 실행되지 않는다.
			} catch (ArithmeticException ae)	{
				System.out.println(5);
			}	// try-catch의 끝
			System.out.println(6);
	}	// main메서드의 끝
}

try 0/0 에서 exception이 발생하면 try 의 남은 것들 수행하지 않고 바로 catch문으로 가서 ,지금 일어난 exception에 해당하는 , 혹은 지금 일어난 exception이 포함될 수 있는 exception이 있는지 검사 진행

1.5 예외 발생, catch 블럭

  • catch 블럭은 괄호()와 블럭{}으로 이뤄짐
  • 괄호() 안에는 처리하고자 하는 예외가 같은 타입의 참조변수 선언하기
  • 예외 발생 시 발생한 예외에 해당하는 클래스 인스턴스 made

FLOW
1) 예외 발생한다면 해당 예외 클래스의 인스턴스가 만들어진다
(EX : ArithmeticException 발생 시, 얘의 인스턴스가 생성)
2) 예외 발생 문장이 try 블럭에 포함된다면 이 예외 처리 가능한 catch 블럭 있는지 찾게 됨
3) 찾으면서 catch 블럭 안에 있는 참조변수의 종류 & 생성된 예외클래스 인스턴스 를 instance of 연산자를 이용해서 검사 진행

  • 이때 모든 예외클래스는 Exception 클래스의 자손

  • catch() 블럭에서 Exception 클래스 타입의 참조변수 선언 시 , 어떤 예외가 다 발생해도 catch가 잡아준다 (모든 인스턴스는 instanceof(Exception) 이 true가 되므로)

  • catch 에서 예외 발생 시 참조변수를 통해 인스턴스에 접근 가능

catch (AtirhmeticException ae){
	ae.printStackTrace(); #ae를 통해 AtirhmeticException 인스턴스 접근 가능 
}
  • printStackTrace : 예외 발생 당시 호출 스택에 있던 메서드 정보와 예외 메시지 화면에 출력 가능
  • getMessage : 발생 예외클래스의 인스턴스에 저장된 메시지 읽기 가능

(+) 멀티 catch 블럭 (교재 423)

try{
 
}catch(예외1 e1){
    예외1 처리 로직
}catch(예외2 e2){
    예외2 처리 로직
}catch(예외3 e3){                                               
    예외3 처리 로직
}
 

출처: https://dololak.tistory.com/61 [코끼리를 냉장고에 넣는 방법:티스토리]

=> 멀티 캐치 블럭을 사용한다면

try{
 
}catch(예외1 | 예외2 | 예외3 e){
   공통 예외로직 ex)e.printStackTrace();
}

출처: https://dololak.tistory.com/61 [코끼리를 냉장고에 넣는 방법:티스토리]

로 간소화 시키기 가능

1.6 예외 고의로 발생시키기 throw !

  1. 연산자 new를 이용해 발생시키려는 예외 클래스 객체 만들기
  • Exception e = new Exception("고의 발생")
  1. throw 이용해 예외 발생시키기
  • throw e
  • 다만 아래의 코드는 컴파일 에러 (문법 상 에러) 발생

1) Exception을 throw 할 시 (CHECKED)

class ExceptionEx10 {
	public static void main(String[] args) {
		throw new Exception();		// Exception 고의 발생
	}
}
  • 예외처리가 돼야할 부분이 돼있지 않아서 발생하는 에러
  • 자바에선 Exception 클래스들 (Exception 클래스와 자손들)이 발생 가능한 문장들을 예외처리 (catch) 안해주면 컴파일 불가능
  • 예외 처리 해주는 Exception 클래스들은 checked 예외라고 불림

2) RuntimeException throw 할 시 (UNCHECKED)

class ExceptionEx11 {
	public static void main(String[] args) {
		throw new RuntimeException();	// RuntimeException 고의 발생 
	}
}
  • 에러 발생 X
  • Runtime Exception 클래스와 그 자손들에 해당하는 예외는 프로그래머에 의해 실수로 발생하는 것 -> 예외 처리 강제당하지 x
  • 컴파일러가 예외 처리 확인 x RuntimeException 클래스들은 unchecked 예외 라고 불림

1.7 메서드에 예외 선언

  • 메서드에 예외를 처리하려면 메서들의 선언부에 키워드 throws 를 사용해 메서드 내에서 발생가능한 예외들 적어주면 된다.
static void method1() throws Exception1,  Exception2, Exception3 {
	//메서드 내용 
} 
  • 메서드 내에서 발생할 가능성이 있는 예외를 메서드 선언부에 예외 선언하면
    : 메서드 사용하려는 사람이 이 메서드 사용 위해선 어떤 예외 처리 필요한지 알기 가능

  • 자바에서는 메서드 작성 시 메서드 내에서 발생 가능한 예외를 메서드 선언부에 명시해서 이 메서드 사용하는 쪽에서는 이에 대한 처리 강요함
    => 이전에 메서드에 예외 선언 하지 않을 때는, 경험 많은 프로그래머가 아니고서는 어떤 상황에 어떤 종류의 예외 발생 가능한지 예측 힘듦 & 대비 어려웠음

  • 메서드에 선언되는 예외는 주로 unchecked 가 아닌, 반드시 처리해주어야만 하는 예외들뿐이다.


  • 또한 예외를 메서드 throws에 명시하는 것은 예외 처리가 아니라 자신을 호출한 메서드에게 예외 전달해 예외 처리 떠맡기는 것
public class ExceptionTest14 {
	static void method1() throws Exception { 
    	// method1을 호출한 메소드로 예외 처리 넘김
		method2(); // 2) 
	}
	static void method2() throws Exception { 
	// method2를 호출한 메소드로 예외 처리 넘김
		throw new Exception(); // Exception 발생 // 1) 
	}
	public static void main(String[] args) throws Exception {
		method1(); //3)
	}
}
  • 예외를 전달받은 메서드가 자신을 호출한 메서드에게 예외 전달 가능, 이런 식으로 계속 호출 스택에 있는 메서드들 따라다가 최후 main 에서도 처리 안되면 프로그램 종료
  • 1) 메소드 2에서 예외 발생, 그러나 try catch 로 예외처리 X
    => 메소드2 종료되면서 예외를 자기를 호출한 method1에게 넘겨줌
  • 2) 메소드 1에섣 예외 처리해주는 부분 존재 x
    => 자신 호출한 main 메소드에게 예외 넘김
  • 3) main에서조차 예외처리 x -> main 메서드 종료 , 프로그램 비정상 종료

예외가 발생한 메서드에서 예외처리 하지 않고 자신 호출한 메서드에게 예외를 토스할 수 있음, 그러나 이것으로 예외처리 된 것이 아니라 반드시 어느 한 지점에서는 예외를 처리해주는 과정이 피필요한 것이지.

class ExceptionEx13 {
	public static void main(String[] args) {
		method1(); 	  
        // 같은 클래스내의 static멤버이므로 객체생성없이 직접 호출가능.
  	}	// main메서드의 끝

	static void method1() {
		try {
		     throw new Exception();
		} catch (Exception e) {
			System.out.println("method1메서드에서 예외가 처리되었습니다.");
			e.printStackTrace();
		}
	}	// method1의 끝
}
  • 이렇게 메소드1에서 처리를 완료해주면 적절하게 처리가 된다.

1.8 FINALLY 블럭

try {
	// Exception이 발생할 가능성이
	있는 문장들을 넣는다.
	} catch (Exception1 e1) {
		// Exception1 이 발생했을 경우, 
		이를 처리하기 위한 문장을 넣는다.
	} catch (Exception2 e2) {
		// Exception2 이 발생했을 경우, 
		이를 처리하기 위한 문장을 넣는다.
	} 
.....................
	catch (ExceptionN eN) {
		// ExceptionN 이 발생했을 경우, 
		이를 처리하기 위한 문장을 넣는다.
	}
	finally { 
		무조건 수행되는 부분
	}
  • try 블럭에서 return 문이 실행되는 경우에도 finally 블럭의 문장들 먼저 실행 후에 실행 중인 메소드 종료
class FinallyTest3 {
	public static void main(String args[]) {
		// method1()은 static메서드이므로 인스턴스 생성없이 직접 호출이 가능하다.
		FinallyTest3.method1();		
        System.out.println("method1()의 수행을 마치고 main메서드로 돌아왔습니다.");
	}	// main메서드의 끝

	static void method1() {
		try {
			System.out.println("method1()이 호출되었습니다.");
			return;		// 현재 실행 중인 메서드를 종료한다.
		}	catch (Exception e)	{
			e.printStackTrace();
		} finally {
			System.out.println("method1()의 finally블럭이 실행되었습니다.");
		}
	}	// method1메서드의 끝
}

1.10 사용자정의 예외 만들기

  • 기존 정의 예외 클래스 외에 프로그래머가 새로운 예외 클래스 정의해 사용 가능
  • 보통 eXCEPTION 클래스, RuntimeException 클래스로부터 상속받아 클래스 만드는데, 필요에 따라 알맞은 예외 클래스 선택하기 가능
  • 요즘은 checked 예외가 아닌 예외처리를 선택적으로 할 수 있는 RuntimeException 상속받아 작성하는 경우 多
  • checked 예외는 반드시 예외처리 해줘야 해서 예외처리가 불필요한 경우에도 try catch문 넣어서 코드가 복잡해지기 때문
class NewExceptionTest {
	public static void main(String args[]) {
		try {
			startInstall();		// 프로그램 설치에 필요한 준비를 한다.
			copyFiles();			// 파일들을 복사한다. 
		} catch (SpaceException e)	{
			System.out.println("에러 메시지 : " + e.getMessage());
			e.printStackTrace();
			System.out.println("공간을 확보한 후에 다시 설치하시기 바랍니다.");
		} catch (MemoryException me)	{
			System.out.println("에러 메시지 : " + me.getMessage());
			me.printStackTrace();
			System.gc();		//  Garbage Collection을 수행하여 메모리를 늘려준다.
			System.out.println("다시 설치를 시도하세요.");
		} finally {
			deleteTempFiles();	// 프로그램 설치에 사용된 임시파일들을 삭제한다.
		} // try의 끝
	} // main의 끝

   static void startInstall() throws SpaceException, MemoryException { 
		if(!enoughSpace()) 		// 충분한 설치 공간이 없으면...
			throw new SpaceException("설치할 공간이 부족합니다.");
		if (!enoughMemory())		// 충분한 메모리가 없으면...
			throw new MemoryException("메모리가 부족합니다.");
   } // startInstall메서드의 끝

   static void copyFiles() { /* 파일들을 복사하는 코드를 적는다. */ }
   static void deleteTempFiles() { /* 임시파일들을 삭제하는 코드를 적는다.*/}
   
   static boolean enoughSpace()   {
		// 설치하는데 필요한 공간이 있는지 확인하는 코드를 적는다.
		return false;
   }
   static boolean enoughMemory() {
		// 설치하는데 필요한 메모리공간이 있는지 확인하는 코드를 적는다.
		return true;
   }
} // ExceptionTest클래스의 끝

class SpaceException extends Exception {
	SpaceException(String msg) {
	   super(msg);	
   }
} 

class MemoryException extends Exception {
	MemoryException(String msg) {
	   super(msg);	
   }
}
  • 교재에서 제시된 MemoryException, SpaceException 사용자 정의 예외

1.12 연결된 예외 (chained Exception)

  • 한 예외가 다른 예외 발생시키기 가능
  • 예외 A가 B를 발생시키면 , A는 B의 원인 예외 (CAUSE EXCEPTION)
  • 여러 예외를 하나로 묶어서 다루기 위해서 사용하는 것

Throwable initCause (Throwable cause) : 지정 예외를 원인 예외로 등록
Throwable getCause() : 원인 예외를 반환

(ex)

  • 공간예외와 메모리예외가 일어나면 이 두 예외를 Install Exception의 발생 원인인 예외들로 등록해준다 (initCause)
	static void install() throws InstallException {
		try {
			startInstall();		// 프로그램 설치에 필요한 준비를 한다.
			copyFiles();		// 파일들을 복사한다. 
		} catch (SpaceException e)	{
			InstallException ie = new InstallException("설치중 예외발생");
			ie.initCause(e);
			throw ie;
		} catch (MemoryException me) {
			InstallException ie = new InstallException("설치중 예외발생");
			ie.initCause(me);
			throw ie;
		} finally {
			deleteTempFiles();	// 프로그램 설치에 사용된 임시파일들을 삭제한다.
		} // try의 끝
	}
  • 이렇게 두 예외를 Install예외의 조상으로 처리 하지 않았더라면, install() 메소드를 실행하면서 발생할 수 있는 예외는 두가지이므로
    나중에 main에서 install 메소드 실행할 때 각각 예외에 대해서 다 처리해줘야 함,

  • 반면 위와 같이 처리를 해줌으로써 main에서 install을 try 할 때 installexcpetion 하나에 대해서만 catch를 작성해주면 ok

	public static void main(String args[]) {
		try {
			install();
		} catch(InstallException e) {
			e.printStackTrace();
		} catch(Exception e) {
			e.printStackTrace();		
		}
	} // main의 끝

< total 코드>

class ChainedExceptionEx {
	public static void main(String args[]) {
		try {
			install();
		} catch(InstallException e) {
			e.printStackTrace();
		} catch(Exception e) {
			e.printStackTrace();		
		}
	} // main의 끝

	static void install() throws InstallException {
		try {
			startInstall();		// 프로그램 설치에 필요한 준비를 한다.
			copyFiles();		// 파일들을 복사한다. 
		} catch (SpaceException e)	{
			InstallException ie = new InstallException("설치중 예외발생");
			ie.initCause(e);
			throw ie;
		} catch (MemoryException me) {
			InstallException ie = new InstallException("설치중 예외발생");
			ie.initCause(me);
			throw ie;
		} finally {
			deleteTempFiles();	// 프로그램 설치에 사용된 임시파일들을 삭제한다.
		} // try의 끝
	}

static void startInstall() throws SpaceException, MemoryException { 
	if(!enoughSpace()) { 		// 충분한 설치 공간이 없으면...
		throw new SpaceException("설치할 공간이 부족합니다.");
	}

	if (!enoughMemory()) {		// 충분한 메모리가 없으면...
		throw new MemoryException("메모리가 부족합니다.");
//		throw new RuntimeException(new MemoryException("메모리가 부족합니다."));
	}
} // startInstall메서드의 끝

   static void copyFiles() { /* 파일들을 복사하는 코드를 적는다. */ }
   static void deleteTempFiles() { /* 임시파일들을 삭제하는 코드를 적는다.*/}
   
   static boolean enoughSpace()   {
		// 설치하는데 필요한 공간이 있는지 확인하는 코드를 적는다.
		return false;
   }
   static boolean enoughMemory() {
		// 설치하는데 필요한 메모리공간이 있는지 확인하는 코드를 적는다.
		return true;
   }
} // ExceptionTest클래스의 끝

class InstallException extends Exception {
	InstallException(String msg) {
	   super(msg);	
   }
} 

class SpaceException extends Exception {
	SpaceException(String msg) {
	   super(msg);	
   }
} 

class MemoryException extends Exception {
	MemoryException(String msg) {
	   super(msg);	
   }
}

코드 및 내용 출처 : 자바의 정석 교재 및 깃허브

0개의 댓글