[Java의 정석] 예외 처리 (Exception Handling)

Shiba·2023년 6월 5일
post-thumbnail

📓 예외 처리 (Exception Handling)

📃 오류의 종류

🔹 컴파일 에러(compile-time error)

컴파일 할 때 발생하는 에러 - 실행이 안됨

system.out.println(); //컴파일 에러!! system -> System으로 수정

🔹 런타임 에러(runtime error)

실행 할 때 발생하는 에러 - 발생 시 프로그램 종료

int[] arr = new int[5]; // a[0~4]

for(int i = 0; i<=5; i++){
	System.out.println(arr[i]); //런타임 에러!! 배열의 범위를 벗어남 (a[5])
}

🔹 논리적 에러(logical error)

작성 의도와 다르게 동작 (맞왜틀) - 발생 시 프로그램 종료X

//맞는데 왜 틀림?//
//배열의 합을 구하는 프로그램
long[] arr = new long[5]; // a[0~4]
int sum = 0;
for(int i = 0; i<=5; i++){
	sum += arr[i]; 
}
System.out.println(sum); 
//배열의 합이 int를 벗어나면 결과값이 의도와 다르게 나옴

📃 에러와 예외

🔹 에러 (error)

런타임 에러중에 코드에 의해서 수습될 수 없는 심각한 오류
-메모리 부족

🔹 예외 (exception)

런타임 에러중에 코드에 의해서 수습될 수 있는 다소 미약한 오류
-0으로 나누기, 배열범위 초과, 형변환 오류

📃 예외 클래스의 계층 구조

🔹 Exception과 RuntimeException

◼ Exception클래스와 자손들

사용자의 실수와 같은 외적인 요인에 의해 발생하는 예외

◼ RuntimeException클래스와 자손들

프로그래머의 실수로 발생하는 예외

📃 예외의 종류

🔹 checked예외

컴파일러예외 처리 여부체크 (예외 처리 필수)
- 안하면 컴파일 에러

🔹 unchecked예외

컴파일러예외 처리 여부 체크 X (예외 처리 선택)
-안하면 런타임 에러

🔹 예외처리가 모두 필수가 아닌 이유

모두 필수로 처리를 해야한다면 거의 모든 코드에 예외처리를 해줘야 함.

//모두 필수로 예외처리를 해야한다면 아래 코드에서도 예외처리를 해주어야 한다!

int[] arr = new int[10];
System.out.println(arr[0]); 

//배열 범위 초과 에러, null포인터 에러가 발생할 수 있으므로 예외처리해주어야함.

📃 예외 발생시키기

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

Exception e = new Exception("고의로 발생시켰음"); //new로 객체를 생성한 다음~
throw e; //예외를 발생시킨다!

// throw new Exception("고의로 발생시켰음"); // 두줄을 한줄로

📃 예외 처리의 정의와 목적

🔹 정의

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

🔹 목적

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

📃 예외 처리 방법

🔹 try-catch문

◼ 작성

try{
	//예외가 발생할 가능성이 있는 문장들을 넣는다
} catch (Exception e1) {
	//Exception e1이 발생했을 경우, 이를 처리하기 위한 문장을 넣는다
} catch (Exception e2) {
	//Exception e2가 발생했을 경우, 이를 처리하기 위한 문장을 넣는다
} catch (Exception eN) {
	//Exception eN이 발생했을 경우, 이를 처리하기 위한 문장을 넣는다
}

◼ 흐름

● try블럭 내에서 예외가 발생한 경우

  1. 발생한 예외일치하는 catch문이 있는지 확인
  2. 일치하는 catch블럭 내의 문장들을 수행하고 전체 try-catch문빠져나가서 그 다음 문장을 계속해서 수행.
    • 일치하는 catch블럭없으면 예외는 처리되지 못한다.
public static void main(String args[]){
	System.out.println(1);
    try{
    	System.out.println(0/0); //예외발생!!
    	System.out.println(2); // 실행X
    } catch (ArithmeticException ae) { //예외 처리
    	System.out.println(3);
    } catch (Exception e) { //ArithmeticException을 제외한 모든 예외 처리
    // 위의 catch문이 catch했으므로 그냥 지나감
    	System.out.println(4);
    } //try-catch문의 끝
    System.out.println(5);
}

//출력
1
3
5

● try블럭 내에서 예외가 발생하지 않은 경우

catch블럭을 거치지 않고 전체 try-catch문빠져나가서 수행을 계속한다

public static void main(String args[]){
	System.out.println(1);
    try{
    	System.out.println(2);
        System.out.println(3);
    } catch (exception e) { //예외발생X - 그냥 지나감
    	System.out.println(4); //실행X
    } //try-catch문의 끝
    System.out.println(5);
}

//출력
1
2
3
5

◼ printStackTrace()와 getMessage()

● printStackTrace()

예외발생 당시의 호출스택에 있었던 메소드 정보, 예외 메세지출력

● getMessage()

발생한 예외클래스인스턴스저장된 메세지를 얻을 수 있음

class EX8_5{
	public static void main =(String args[]){
    	System.out.println(1);
        System.out.println(2);
        
        try{
        	System.out.println(3);
            System.out.println(0/0); //8번째줄 예외 발생!!
            System.out.println(4); //실행안됨
        } catch (ArithmeticException ae) {
        	//printStackTrace()
        	ae.printStackTrace(); 
            
            //getMessage()
            System.out.println("예외메세지 : " + ae.getMessage); 
        } // try-catch의 끝
    	
        System.out.println(6);
    }
}

//출력
1
2
3
java.lang.ArithmeticException: /by zero at EX8_5.main(EX8_5.java:8)
예외메세지 : / by zero
6

◼ finally블럭

예외 발생여부관계없이 수행되어야 하는 코드를 넣는다.

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

◼ 멀티 catch블럭 - (JDK1.7부터)

내용이 같은 catch블럭하나로 합친 것

try{
	//...
} catch(ExceptionA e1) {
	e1.printStackTrace();
} catch(ExceptionB e2) {
	e2.printStackTrace();
}


//멀티 catch 문
try {
	//...
} catch (ExceptionA | ExceptionB e){ 
	e.printStackTrace();
}

예외들이 부모-자식관계라면 사용X

try {
	//...
} //catch (ParentException | ChildException e) { //에러!
  catch(ParentException e) { //위 라인과 의미상 동일
	e.printStackTrace();
}

예외에 선언된 메소드호출불가

try {
	//...
} catch (ExceptionA | ExceptionB e){ 
	// e.methodA(); //에러!! ExceptionA에 선언된 methodA는 호출 불가
    
    //사용하려면 아래와 같은방법으로 수행
    if(e instanceof ExceptionA) {
    	ExceptionA e1 = (ExceptionA) e; //형변환하기
        e1.methodA(); // 호출가능
    } else { //if(e instanceof ExceptionB)
    	//...
    }
}

🔹 예외 선언하기 (예외 떠넘기기)

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

void method() throws Exception1, Exception2, ... ExceptionN {
	//메소드의 내용
}

//예제
static void startInstall() throws SpaceException, MemoryException {
	if(!enoughSpace())
    	throw new SpaceException("설치 공간이 부족합니다.");
    if(!enoughMemory())
    	throw new MemoryException("메모리가 부족합니다.");
}

에러를 발생시키는 키워드 throw예외를 메소드에 선언할 때 쓰는 throws 잘 구별하기

📃 사용자 정의 예외 만들기

우리가 직접 상속을 통해 예외 클래스를 정의
- 조상은 ExceptionRuntimeException중에서 선택

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

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

  • 예외를 처리한 후다시 예외를 발생시키는 것
  • 호출한 메소드호출된 메소드 양쪽 모두에서 예외처리하는 것
class EX8 {
	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; //예외 다시 발생
        }
    }
}

🔷 예외를 다시 던지는 이유

예외 처리를 양쪽에서 분담해서 처리가 가능해짐

📃 연결된 예외 (chained exception)

  • 한 예외다른예외를 발생시킬 수 있다.
  • 예외 A가 예외 B를 발생시키면, A는 B의 원인 예외(cause exception)
Throwable initCause(Throwable cause) // 지정한 예외를 원인 예외로 등록
Throwable getCause() //원인 예외를 반환

//예제

public class Throwavle implements Serializable {
	private Throwable cause = this; //객체 자신(this)를 원인 예외로 등록
    //...
    public synchronized Throwable initCause(Throwable cause){
    	this.cause = cause; //cause를 원인 예외로 등록
        return this;
    }
}

🔷 사용 이유

◼ 여러 예외를 하나로 묶어서 다루기 위해서

try {
	install();
} catch (SpaceException e) { //예외 1
    	e. printStackTrace();
} catch (MemoryException e) { //예외 2
    	//...
} catch (Exception e) {
	//...
}

// 위 코드를 아래 코드로 줄일 수 있다

try {
	install();
} catch (InstallException e) { 
    	e. printStackTrace();
} catch (Exception e) {
	//...
}


void install() throws InstallException {
	try{
    	startInstall();
    	copyFiles();
    } catch (SpaceException e) { //예외 1
    	InstallException ie = new InstallException("설치중 예외발생");
        ie.initCause(e); //원인예외 지정
        throw ie; //예외 발생
    } catch (MemoryException me) { //예외 2
    	//...
    }
}

◼ checked예외를 unchecked예외로 변경하려 할 때

static void startInstall() throws SpaceException, MemoryException {
	if(!enoughSpace())
    //필수처리예외
    	throw new SpaceException("설치 공간이 부족합니다.");
    if(!enoughMemory())
    //필수처리예외
    	throw new MemoryException("메모리가 부족합니다.");
}

//위 코드를 변경

static void startInstall() throws SpaceException {
	if(!enoughSpace())
    //필수처리예외
    	throw new SpaceException("설치 공간이 부족합니다.");
    if(!enoughMemory())
    	//RuntimeException이 MemoryException을 원인예외로 등록
        //선택처리예외
    	throw new RuntimeException(new MemoryException("메모리가 부족합니다."));
}
profile
모르는 것 정리하기

0개의 댓글