Java/Java의 정석

Chapter 08 예외처리 Exception Handling

계란💕 2022. 2. 23. 12:27

1. 예외처리(Exception Handling)

  Def) 예외: 컴파일도 되고 실행도 되지만 의도와 다른게 동작하는 것

 

  1.1 프로그램 오류

  - 컴파일 에러: 컴파일 시에 발생하는 에러 ex) system.out.println(); => 오류

  - 런타임에러: 실행 시에 발생하는 에러

  - 논리적 에러: 실행되지만, 의도와 다르게 동작하는 것

  - 에러와 예외 차이

    -> 에러: 코드에 의해 수습될 수 없는 심각한 오류

    -> 예외: 코드로 수습할 정도의 미약한 오류(실행 잘 되지만 의도와 다르게 동작)

  - 예외처리의 정의, 목적

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

  - 목적: 비정상 종료를 막고, 정상적 실행상태를 유지한다. 

 

  1.2 예외 클래스의 계층구도

  - 모든 예외는 Exception(모든 예외의 최고 조상)의 자손 클래스

  - checked예외: 컴파일러가 예외처리 여부를 체크(예외 처리(try-catch) 필수)

    -> Exception클래스와 그 자손들,  RuntimeException는 제외)

  - unchecked예외: 컴파일러가 예외처리 여부를 체크하지 않는다 (예외 처리 선택) -  (RuntimeException와 자손들)

  - RuntimeException클래스들은 주로 프로그래머의 실수에 의해 발생될 수 있는 예외들이다. 

  - Exception클래스들은 주로 외부의 영향으로 발생할 수 있는 것들이다.

    -> 주로 프로그램 사용자의 동작에 의해 발생한다.

 

  1.3 예외처리하기 try - catch문

  - 예외발생하면 이를 처리할 catch블럭을 찾아 내려간다.

  - 일치하는 catch블럭을 찾지 못하면 예외는 처리되지 못한다. 

  - Exception 선언된 catch블럭은 맨 마지막 블럭.

    

<hide/>

try{
	// 예외가 발생할 가능성이 있는 문장들을 넣는다.
}catch (Exception1 e1) {
	// Exception1이 발생했을 때 처리를 위한 문장을 넣는다.
}catch (Exception2 e2) {
	// Exception2이 발생했을 때 처리를 위한 문장을 넣는다.
}catch (Exception3 e3) {
	// Exception3이 발생했을 때 처리를 위한 문장을 넣는다.
}

  Note) 하나의 try블럭 다음에는 여러 종류의 예외를 처리할 수 있도록 하나 이상의 catch블럭이 올 수 있다.

  - 이 중에서 발생한 예외와 일치하는 단 한 개의 catch블럭만 수행된다. 

  - 일치하는 예외가 없으면 처리 되지 않는다.

   - catch블럭 내에 또 다른 try-catch문이 포함된 경우에는 같은 이름의 참조변수를 사용할 수 없다.

 

  1.5 예외발생과 catch블럭

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

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

  - 멀티 catch블럭: 내용이 같은 catch블럭을 하나로 합친 것

 

  1.6 예외 발생시키기 

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

    2) 키워드 throw를 이용해서 예외를 발생시킨다.

  Ex)

<hide/>
package javaStudy;
public class ExceptionEx9 {
	public static void main(String[] args) {
		try {
			Exception e = new Exception("고의로 발생시켰음.");  
			throw e;  		//예외를 발생시킴
//			throw new Exception("고의로 발생시켰음.");
		}catch(Exception e) {
			System.out.println("에러메시지 : " + e.getMessage());
			e.printStackTrace();
		}
		System.out.println("프로그램이 정상 종료되었음");	
	}
}

  Note)

  - 예외 발생시키는 두 줄을 간략히, throw new exception("고의로 발생시켰음."); 으로 나타낼 수 있다.

 

  1.7 메서드에 예외 선언하기

  - 메서드가 호출시 발생가능한 예외를 호출하는 쪽에 알리는 것

<hide/>
void method() throws Exception1, Exception2, ... , ExceptionN{
	// 메서드의 내용
}

  - 만일 위에 Exception만 선언하면 예외뿐만 아니라  자손타입의 예외까지도 발생할 가능성이 있다는 것이다.

  - 오버라이딩 할 때는 선언된 예외의 개수가 아니라 상속관계까지 고려해야한다.

 

  Ex)

<hide/>
package javaStudy;
public class ExceptionEx12 {
	public static void main(String[] args) throws Exception {
		method1();	// 같은 클래스내의 static멤버이므로 객체생성없이 직접 호출 가능.
	}
	static void method1() throws Exception {
		method2();
	}
	static void method2() throws Exception{
		throw new Exception();		// 예외 강제로 발생
	}
}

  Note) 실행 결과

  - 프로그램의 실행도중 java.lang.Exception이 발생하여 비정상적으로 종료했다.

  - 예외 발생했을 때의 호출 스택내용을 알 수 있다.

    1) 예외가 발생했을 때, 모두 3개의 메서드가 호출 스택에 있었다.

    2) 예외가 발생한 곳은 제일 윗줄에 있는 method2()

    3) main메서드가 method1() 호출, 그리고 method1()은 method2()를 호출했다. 

 

  - method2에서 예외가 (강제로) 발생했으나 예외처리를 해주지 않았기 때문에 메서드 종료하고

  - 자신을 호출한 method1()에게 예외를 넘겨준다. 

  - 그러나 main메서드에서조차 예외처리를 해주지 않아서 main이 종료되어

  - 프로그램이 예외로 인해 비정상적으로 종료된다.

  - 따라서 어느 한 곳에서 반드시 try-catch문으로 예외처리해야한다.

 

  Ex 8-15)

package javaStudy;
import java.io.*;
public class ExceptionEx15 {
	public static void main(String[] args) {
		// command line에서 입력받은 값을 이름으로 갖는 파일을 생성한다.
		File f = createFile(args[0]);
		System.out.println(f.getName() + " 파일이 성공적으로 생성되었습니다."  );	
	}
	static File createFile(String fileName) {
		try {
			if( fileName == null || fileName.equals("") ) 
				throw new Exception("파일이름이 유효하지 않습니다.");
		}catch (Exception e){
			// fileName이 부적절한 경우, 파일이름을 '제목없음.txt'로 한다.
			fileName = "제목없음.txt";
		}finally {
			File f  = new File(fileName); // File클래스의 객체를 만든다.
			createNewFile(f);		// 생성된 객체를 이용해서 파일을 생성한다.
			return f;
		}
	}
	static void createNewFile(File f) {
		try {
			f.createNewFile();	// 파일을 생성한다. 
			
		}catch(Exception e) {}	
	}
}

  Note)

  - 이 예제는 예외가 발생한 메서드에서 직접 예외를 처리하도록 되어 있다. 

  - createFile메서드를 호출한 main메서드에서는 예외가 발생한 사실을 알지 못한다. 

 

  1.8 finally블럭

  - 예외발생 여부와 관계없이 수행되어야 하는 코드

<hide/>

try{
	// 예외가 발생할 가능성이 있는 문장들을 넣는다.
}catch (Exception e) {
	// 예외처리를 위한 문장을 넣는다.
}finally{
	// 예외 발생여부와 관계 없이 항상 수행할 문장
    // finally블럭은 try-catch문의 맨 마지막에 위치해야한다.
}

 

  1.9 자동 자원 반환 - try - with - resources 문

  - try-catch문의 변형이다.

  - 입출력에 사용되는 클래스 중에서는 사용 후에 꼭 닫아줘야하는 것들이 있다. 

  - 그래야 사용했던 자원이 반환(resource)되기 때문이다.

  Ex)

<hide/>
package javaStudy;
public class TryWithResourceEx {
	public static void main(String[] args) {
		try (CloseableResource cr = new CloseableResource () ){
			cr.exceptionWork(false);		// 에외가 발생하지 않늗다.	
		}catch(WorkException e) {
			e.printStackTrace();
		}catch(CloseException e) {
			e.printStackTrace();
		}
		System.out.println();
		try (CloseableResource cr = new CloseableResource () ){
			cr.exceptionWork(true);		// 에외가 발생하지 않늗다.
		}catch(WorkException e) {
			e.printStackTrace();
		}catch(CloseException e) {
			e.printStackTrace();
		}
	}
}
class CloseableResource implements AutoCloseable {
	public void exceptionWork(boolean exception) throws WorkException{
		System.out.println( "exceptionWork(" + exception + ")가 호출됨" );
		if(exception)
			throw new WorkException("WorkException발생!!!");
	}
	public void close() throws CloseException{
		System.out.println("close()가 호출됨");
		throw new CloseException("CloseException발생!!!");
	}
}
class WorkException extends Exception{
	WorkException(String msg){super(msg);	}
}
class CloseException extends Exception{
	CloseException(String msg){super(msg);	}
}

  Note) 실행 결과

  - main메서드에 두 개의 try-catch문이 있는데 첫 번째 것은 close()에서만 예외를 발생시키고

  - 두 번째 것은 exceptionWork()와 close()에서 모두 예외를 발생시킨다.

  - 첫번째 것은 일반적인 예외가 발생했을 때와 같은 형태

  - 두 번째는 출력형태가 다른.

  - 두 예외가 동시에 발생할 수는 없다. 실제 발생한 예외를 'WorkException'이라고한다.

  - CloseException은 억제된 예외이다.

    -> void addSuppressed(Throwable exception) : 억제된 예외를 추가

    -> Throwable [] getSuppressed() : 억제된 예외(배열)을 반환한다. 

 

  1.10 사용자 정의 예외 만들기

  - 우리가 직접 예외를 정의할 수 있다.

  - Exception과 RuntimeException 중에 조상을 선택한다. 

<hide/>
class MyException estends Exception{
	MyException(String msg){	//문자열을 매개변수로 받는 생성자
    	super(msg);				// 조상인 Exception클래스의 생성자를 호출
    }
}

 

  Ex)

<hide/>
package javaStudy;
public class NewExceptioneTest {
	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();		// 프로그램 설치를 사용된 임시파일들을 삭제한다. 
  		}
	}
	static void startInstall() throws SpaceException, MemoryException{
		if( !enoughSpace() )		// 충분한 설치 공간이 없으면
			throw new SpaceException("설치할 공간이 부족합니다.");
		if( !enoughMemory() )		// 충분한 설치 공간이 없으면
			throw new MemoryException("메모리가 부족합니다.");
	}
	static void copyFiles() {  /*파일들을 복사하는 코드 적는다.*/ }
	static void deleteTempFiles() {  /* 임시파일들을 삭제하는 코드 적는다.*/ }
	static boolean enoughSpace() {
		// 설치하는데 필요한 공간이 있는지 확인하는 코드를 적는다. 
		return false;
	}
	static boolean enoughMemory() {
		// 설치하는데 필요한 공간이 있는지 확인하는 코드를 적는다. 
		return true;
	}
}
class SpaceException extends Exception{
	SpaceException(String msg){
		super(msg);
	}
}
class MemoryException extends Exception{
	MemoryException(String msg){
		super(msg);
	}
}

  Note) 실행 결과

  - MemoryException, SpaceException 두 개의 사용자 정의 예외 클래스를 새로 만들었다. 

  - 두 예외는 startInstall()을 수행하는 동안 발생할 수 있으며

  - enoughSpace()와 enoughMemory()의 실행 결과에 따라 예외의 종류가 달라지기도한다. 

 

  1.11 예외 되던지기(exception re-throwing)

  - 예외를 처리한 후에 다시 예외를 발생시키는 것

  - 호출한 메서드와 호출된 메서드 양쪽 모두에서 예외처리하는 것

  Ex)

<hide/>
package javaStudy;
public class ExceptionEx17 {
	public static void main(String[] args) {
		try {
			method1();
		}catch(Exception e) {
			System.out.println("main메서드에서 예외가 처리됐습니다.");	
		}
	}
	static void method1() throws Exception{
		try {
			throw new Exception();		
		}catch(Exception e) {
			System.out.println("method1메서드에서 예외가 처리되었습니다.");
			throw e;		// 다시 예외를 발생시킨다.
		}
	}
}

  

  Note) 실행 결과

  - method1()과 main매서드 양쪽의 catch블럭이 모두 수행되었다.

  - method1()의 catch블럭에서 예외를 처리하고도 throw문을 통해 다시 예외발생시켰다

  - 그 예외를 main메서드에서 한 번 더 처리했다.

 

  1.12 연결된 예외(chained exception)

  - 한 예외가 다른 예외를 발생시킬 수 있다.

  - 예외 A가 예외B를 발생시키면 A는 B의 원인예외(cause exception)

<hide/>
Throwable initCause(Throwable cause)		: 지정한 예외를 원인 예외로 등록
Throwable getCause()		: 원인 예외를 반환

  -  initCause()는 throwable 클래스에 정의 되어 있어서 모든 예외에서 사용 가능.

 

  - 연결된 예외를 사용하는 이유

    1) 여러 예외를 하나로 묶어 다루기 위해

    2) checked예외를 unchecked예외로 변경하려할 때 (예외처리가 선택적인 사항이 된다.)

    3) 예외의 상속 관계를 변경하지 않아도 된다.

  Ex)

<hide/>
package javaStudy;
public class ChainedExceptionEx {
	public static void main(String[] args) {
		try {
			install();
		}catch(InstallException e) { // InstallException은 SpaceException과 MemoryException의 조상)
			e.printStackTrace();	
		}catch(Exception e) {
			e.printStackTrace();	
		}
	}
	static void install() throws InstallException{
		try {
			startInstall();		//프로그램 설치에 필요한 준비를 한다.
			copyFiles();
		}catch(SpaceException se) {
			InstallException ie = new InstallException("설치 중 예외발생");
			ie.initCause(se);
			throw ie;
		}catch(MemoryException me) {
			InstallException ie = new InstallException("설치 중 예외발생");
			ie.initCause(me);
			throw ie;
		}finally {
			deleteTempFiles(); 			// 프로그램에 설치에 사용된 임시 파일들 삭제한다.
		}
	}
static void startInstall() throws SpaceException, MemoryException {
	if( !enoughSpace() ) {		// 충분한 설치 공간이 없으면 
		throw new SpaceException("설치할 공간이 부족합니다.");
	}
	if( !enoughMemory() ) {
		throw new MemoryException("메모리가 부족합니다.");
//		throw new RuntimeException(new MemoryException("메모리가 부족합니다."));
	}
}
	static void copyFiles() { /* 파일들 복사하는 코드*/}
	static void deleteTempFiles() { /* 임시파일들 삭제하는 코드*/}
	
	static boolean enoughSpace() {
		// 설피하는데 필요한 공간이 있는지 확인하는코드
		return false;
	}
	static boolean enoughMemory() {
		// 설피하는데 필요한 메모리 공간이 있는지 확인하는코드
		return true;
	}
}
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);
	}
}

  Note) 실행 결과

  - throw new RuntimeException(new MemoryException("메모리가 부족합니다."));

    -> MemoryException은 Exception의 자손이므로 반드시 예외처리를 해야한다. 

    -> 이 예외를 RuntimeException으로 감쌌기 때문에 unchecked예외가 되었다.