본문 바로가기

복습

[Java 복습] 예외처리

프로그램 오류 종류

  • 컴파일 에러(compile-time error): 컴파일 할 때 발생하는 에러
  • 런타임 에러(runtime error): 실행 할 때 발생하는 에러
  • 논리적 에러(logicla error): 작성 의도와 다르게 동작

 

 

Java의 런타임 에러

 

에러는 어쩔 수 없지만, 예외는 처리하자!

  • 에러: 프로그램 코드에 의해서 수습될 수 없는 심각한 오류
  • 예외: 프로그램 코드에 의해서 수습될 수 있는 다소 미약한 오류

 

 

예외처리의 정의와 목적

  • 정의: 프로그램 실행 시 발생할 수 있는 예외의 발생에 대비한 코드를 작성하는 것
  • 목적: 프로그램의 비정상 종료를 막고 정상적인 실행상태를 유지하는 것

 

 

Exception 과 RuntimeException

  • Exception 클래스들: 사용자의 실수와 같은 외적인 요인에 의해 발생하는 예외
  • RuntimeException 클래스들: 프로그래머의 실수로 발생하는 예외

 

try - catch 문

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

    public static void main(String[] args) {
        System.out.println(1); // 1
        try {
            System.out.println(0/0); //2 예외 발생
            System.out.println(2); // 건너뜀
        } catch (ArithmeticException ae) { // 3
            System.out.println(3); // 4
        }
        System.out.println(4); // 5
    }
}

 

 

예외가 발생과 catch 블럭

  • 예외가 발생하면, 이를 처리할 catch 블럭을 찾아 내려감
  • 일치하는 catch 블럭이 없으면, 예외는 처리가 안됨
  • Exception이 선언된 catch 블럭은 모든 예외 처리(마지막 catch 블럭) 
  • Exception이 선언된 catch 블럭을 맨 앞에 놓으면 모든 예외를 처리해버리기 때문에 어떤 예외가 발생했는지 모른다.
public class ExceptionTest2 {

    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) {
            if (ae instanceof ArithmeticException) {
                System.out.println("true");
                System.out.println("ArithmeticException");
            }
        } catch (Exception e) {
            System.out.println("Exception");
        }
        System.out.println(5);
    }
}
1
2
3
true
ArithmeticException
5

 

 

printStackTrace( )와 getMessage( )

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

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

public class ExceptionTest2 {

    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) {
            if (ae instanceof ArithmeticException) {
                ae.printStackTrace();
                System.out.println("예외 메세지: " + ae.getMessage());
            }
        } catch (Exception e) {
            System.out.println("Exception");
        }
        System.out.println(5);
    }
}
1
2
3
예외 메세지: / by zero
5
java.lang.ArithmeticException: / by zero
	at ex.exception.ExceptionTest2.main(ExceptionTest2.java:10) // main 메서드 10번째 줄

 

 

멀티 catch 블럭

  • 내용이 같은 catch 블럭을 하나로 합친 것(JDK 1.7 ~)
  • 내용이 같을 때만 사용
try {
    // 예외가 발생할 가능성이 있는 문장들을 넣는다.
} catch (ExceptionA e1) {
    e.printStackTrace();
} catch (ExceptionB e2) {
    e.printStackTrace();
}

try {
    ...
} catch(ExceptionA | ExceptionB e) { // 부모 자식 관계 X: 어차피 조상타입으로 한번에 처리가능 하기 때문에
    e.printStackTrace();
}

 

  • ExceptionA 만 가지고 있는 메서드는 호출불가 하다.
  • ExceptionA, ExceptionB의 공통 멤버만 사용가능!
  • instanceof로 체크한 후 형변환 후 사용 가능!
try {
    ...
} catch(ExceptionA | ExceptionB e) { 
    e.methodA(); // Error! ExceptionA에 선언된 methodA() 는 호출불가
    
    if(e instanceof ExceptionA) { // e(예외)가 ExceptionA의 예외인가?
        Exception e1 = (ExceptionA)e;
        e1.methodA(); // OK. ExceptionA 에 선언된 메서드 호출가능
    } else {
        // if(e instanceof ExceptionB) {}
}

 

 

 

예외 발생시키기

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

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

 

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

throw e;

 

public class ExceptionTest3 {

    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("프로그램이 정상 종료되었음");
    }
}
에러 메세지: 고의로 발생시켰음
프로그램이 정상 종료되었음
java.lang.Exception: 고의로 발생시켰음
	at ex.exception.ExceptionTest3.main(ExceptionTest3.java:8)

 

 

checked 예외, unchecked 예외

  • checked 예외: 컴파일러 예외 처리 여부를 체크(예외 처리 필수)
    • Exception과 자손: 체크 예외(try- catch 필수)
  • unchecked 예외: 컴파일러가 예외 처리 여부를 체크 안함(예외 처리 선택)
    • RuntimeException과 자손들: 언체크 예외(try - catch 선택)
public class ExceptionTest4 {

    public static void main(String[] args) {
        throw new Exception(); // Exception을 고의로 발생시킨다.(컴파일 오류!!!)
    }
}

 

public class ExceptionTest4 {

    public static void main(String[] args) {
        throw new RuntimeException(); // RuntimeException을 고의로 발생시킨다.(런타임 에러!!)
    }
}

 

 

그럼 RuntimeException은 왜 try-catch가 선택적일까?

 

만약 try-catch가 필수적이라면 배열을 생성할 때 이런 복잡한 코드가 될 것이다.

그럼 거의 모든 코드마다 이런 try-catch를 넣어야 할 것이다.

public class ExceptionTest5 {

    public static void main(String[] args) {

        try {
            int[] arr = new int[10];
            System.out.println(arr[0]);
        } catch (ArrayIndexOutOfBoundsException ae) {
            System.out.println("배열의 인덱스를 벗어났습니다.");
        } catch (NullPointerException ne) {
            System.out.println("배열의 값이 null 입니다");
        }
    }
}

 

 

메서드에 예외 선언하기

예외를 처리하는 방법: try-catch, 예외 선언(떠넘기기). 은폐(try-catch문 빈칸)

 

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

void method() throws Exception1, Exception2, ... { //이런 이런 예외들이 일어날 수 있다.
    // 메서드 내용
    }
    

// Method()에서 Excetion과 그 자손 예외 발생 가능
void method() throws Exception { // 모든 예외의 종류가 발생할 수 있다.
    // 메서드 내용
}
static void startInstall() throws SpaceException, MemoryException { //이런 이런 예외들이 일어날 수 있다.
    if(!enoughSpace()) {
        throw new SpaceException("설치할 공간이 부족합니다");
    }
    if(!enoughMemory()) {
        throw new MemoryException("메모리가 부족합니다.");
    }
}

 

 

예제 1.

public class ExceptionTest6 {

    public static void main(String[] args) throws Exception {
        // 같은 클래스내의 static 멤버이므로 객체 생성없이 직접 호출 가능
        method1(); // 1. 호출 // 6. 4. try-catch문이 없으니 JVM에게 전달하고 비정상 종료
    }

    static void method1() throws Exception {
        method2(); // 2. 호출 // 5. try-catch문이 없으니 자신을 호출한 메서드로 올라감
        // method1 끝
    }

    static void method2() throws Exception {
        throw new Exception(); // 3. 호출 // 4. try-catch문이 없으니 자신을 호출한 메서드로 올라감
        // method2 끝
    }
}
Exception in thread "main" java.lang.Exception
	at ex.exception.ExceptionTest6.method2(ExceptionTest6.java:16)
	at ex.exception.ExceptionTest6.method1(ExceptionTest6.java:11)
	at ex.exception.ExceptionTest6.main(ExceptionTest6.java:7)

 

 

예제 2.

public class ExceptionTest7 {

    public static void main(String[] args) {
        
        try {
            File f = createFile(args[0]);
            System.out.println(f.getName() + "파일이 성공적으로 생성되었습니다");
        } catch (Exception e) {
            System.out.println(e.getMessage() + " 다시 입력해주시기 바랍니다.");
        }
    } // main 종료

    static File createFile(String fileName) throws Exception{
        if (fileName == null | fileName.equals("")) {
            throw new Exception("파일 이름이 유효하지 않습니다");
        }
        File f = new File(fileName); // File 클래스의 객체를 만든다.
        // File 객체의 createNewFile 메서드를 이용해서 실제 파일을 생성한다.
        f.createNewFile(); 
        return f; // 생성된 객체의 참조값 반환
    } // createFile() 끝
}

 

public class ExceptionTest8 {

    public static void main(String[] args) {

        try {
            File f = createFile("");
            System.out.println(f.getName() + " 파일이 성공적으로 생성되었습니다"); // "제목없음" 파일 생성
        } catch (Exception e) { // 예외발생 X, 밑에서 처리했기 때문에
            System.out.println(e.getMessage() + " 다시 입력해주시기 바랍니다.");
        }
    } // main 종료

    static File createFile(String fileName) throws Exception{

        try {
            if (fileName == null | fileName.equals("")) { // true
                throw new Exception("파일 이름이 유효하지 않습니다."); //위 조건을 만족할 때 예외발생
            }
        } catch (Exception e) { // 위에서 잡는 것이 아니라 여기서 바로 잡아버림, 따라서 위에는 상황도 모름
            fileName = "제목없음"; // fileName을 초기화
        }
        File f = new File(fileName);
        f.createNewFile(); 
        return f;
    } // createFile() 끝
}
제목없음 파일이 성공적으로 생성되었습니다

 

예외처리를 작업을 시킨 곳, 작업을 수행한 곳일 지는 우리의 몫!!

 

 

finally 블럭

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

 

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

 

예외가 발생하건 아니건 임시파일을 삭제해야 하는 코드가 있다고 가정하자.

deleteTempFiles( )는 무조건 실행되어야 한다.

try {
    startInstall();
    copyFiles(); // 예외발생!!
    deleteTempFiles();
} catch (Exception e) {
    e.printStackTrace();
    deleteTempFiles();
}

try {
    startInstall();
    copyFiles();
    
} catch (Exception e) {
    e.printStackTrace();
    
} finally {
    deleteTempFiles();
}
  • 코드의 중복이 제거됬다!!
  • 예외 발생 여부와 관계없이 수행되어야 하는 코드를 넣는다.

 

 

사용자 정의 예외 만들기

우리가 직접 예외 클래스를 정의할 수 있다.

조상은 Exception과 RuntimeException 중에서 선택

public class MyException extends Exception{ // 사용자가 일으키는 예외
    MyException(String msg) { // 문자열을 매개변수로 받는 생성자
        super(msg); // 조상인 Exception 클래스의 생성자를 호출한다.
    }
}

 

 

예외 되던지기

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

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

양쪽에서 처리(딸이 조금처리, 아빠가 나머지 처리)

public class ExceptionTest9 {

    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; // 다시 예외를 발생시킨다.
        }
    }
}
method1 메서드에서 예외가 처리되었습니다.
main 메서드에서 예외가 처리되었습니다.

 

 

 

연결된 예외

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

RuntimeException(Throwable cause): 필수 -> 선택
void install() throws InstallException {
    try {
        startInstall(); // SpaceException 발생
        copyFiles();
    } catch (SpaceException e) {
        InstallException ie = new InstallException("설치중 예외발생"); // 예외 생성
        ie.initCaues(e); // InstallException의 원인 예외를 SpaceException으로 지정
        throw ie; // InstallException을 발생시킨다.
    } catch (MemoryException me) {
        ...
    }
}

 

[이유1] 여러 예외를 하나로 묶어서 다루기 위해

try {
    install();
} catch(SpaceException e) {
    e.printStackTrace();
} catch(MemoryException e) {
    e.printStackTrace();
} catch(....) {
    // 무수하게 많은 catch가 있을 수 있다.
} catch(Exception e) {
    e.printStackTrace();
}

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

 

 

[이유2] checked 예외를 unchecked 예외로 변경하려 할 때

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

 

SpaceException을 RuntimeException을 하나 만들고 포함시켜버림.

static void startInstall() throws SpaceExceptionn { 
    if(!enoughSpace()) {
        throw new SpaceException("설치할 공간이 부족합니다");
    }
    if(!enoughMemory()) {
        throw new RuntimeException(new MemoryException("메모리가 부족합니다")); // 필수 -> 선택
    }
}

 

 

'복습' 카테고리의 다른 글

[Java 복습] Object 클래스  (0) 2024.05.03
[Java 복습] hashCode( ), toString( )  (0) 2024.05.03
[Java 복습] 내부 클래스  (0) 2024.05.02
[Java 복습] 인터페이스의 장점  (0) 2024.05.02
[Java 복습] 다형성의 장점  (0) 2024.04.30