자바 도전기-14

김치전사·2022년 1월 15일
0

자바도전기

목록 보기
14/17

오늘은 예외처리를 공부한다

예외처리(exception handling)

프로그램 오류

프로그램이 실행 중 어떤 원인에 의해서 오작동을 하거나 비정상적으로 종료되는 경우가 있다. 이러한 결과를 초래하는 원인을 프로그램 에러 또는 오류라고 한다.

컴파일 에러 : 컴파일 시에 발생하는 에러
런타임 에러 : 실행 시에 발생하는 에러
논리적 에러 : 실행은 되지만, 의도와 다르게 동작하는 것

에러가 발생하면, 프로그램의 비정상적인 종료를 막을 길이 없지만, 예외는 발생하더라도 프로그래머가 이에 대한 적절한 코드를 미래 작성해 놓음으로써 프로그램의 비정상적인 종료를 막을 수 있다.

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

예외 클래스의 계층구조

모든 클래스의 조상은 Object클래스이므로 Exception과 Error클래스 역시 Object클래스의 자손들이다.
RuntimeException클래스와 그 자손 클래스들을 'RuntimeException클래스들'이라 하고, RuntimeException클래스들을 제외한 나머지 클래스들을 'Exception클래스들'이라 하겠다.

RuntimeException클래스들은 주로 프로그래머의 실수에 의해서 발생될 수 있는 예외들로 자바의 프로그래밍 요소들과 관계가 깊다. 예를 들면, 배열의 범위를 벗어난다던가(ArrayIndexOutOfBoundsException), 값이 null인 참조변수의 멤버를 호출하려 했다던가(NullPointerException), 클래스간의 형변환을 잘못했다던가(ClassCastException). 정수를 0으로 나누려고(ArithmeticException)하는 경우에 발생한다

Exception클래스들은 주로 외부의 영향으로 발생할 수 있는 것들로서, 프로그램의 사용자들의 동작에 의해서 발생하는 경우가 많다. 예를 들면, 존재하지 않는 파일의 이름을 입력했다던가(FileNotFoundException), 실수로 클래스의 이름을 잘못 적었다던가(ClassNotFoundException), 또는 입력한 데이터 형식이 잘못된(DataFormatException) 경우에 발생한다

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

예외처리하기 - try-catch문

예외처리(Exception handling)란, 프로그램 실행 시 발생할 수 있는 예기치 못한 예외 발생에 대비한 코드를 작성하는 것이며, 예외처리의 목적은 예외의 발생으로 인한 실행 중인 프로그램의 갑작스런 비정상 종료를 막고, 정상적인 실행상태를 유지할 수 있도록 하는 것이다.

예외처리(exception handling)의
정의- 프로그램 실행 시 발생할 수 있는 예외에 대비한 코드를 작성하는 것
목적- 프로그램의 비정상 종료를 막고, 정상적인 실행상태를 유지하는 것

예외를 처리하기 위해서는 try-catch문을 사용하며, 그 구조는 다음과 같다

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

try-catch문에서의 흐름

try블럭 내에서 예외가 발생한 경우,
1. 발생한 예외와 일치하는 catch블럭이 있는지 확인한다
2. 일치하는 catch블럭을 찾게 되면, 그 catch블럭 내의 문장들을 수행하고 전체 try-catch문을 빠져나가서 그 다음 문장을 계속해서 수행한다. 만일 일치하는 catch블럭을 찾지 못하면, 예외는 처리되지 못한다
try블럭 내에서 예외가 발생하지 않은 경우,
1. catch블럭을 거치지 않고 전체 try-catch문을 빠져나가서 수행을 계속한다

class ExceptionEx{
    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);
        }
        System.out.println(6);
    }
}

예외의 발생과 catch블럭

catch블럭은 괄호()와 블럭{} 두 부분으로 나눠져 있는데, 괄호()내에는 처리하고자 하는 예외와 같은 타입의 참조변수 하나를 선언해야한다

printStackTrace( ) : 예외발생 당시의 호출스택(Call Stack)에 있었던 메서드의 정보와 예외 메시지를 화면에 출력한다
getMessage( ) : 발생한 예외클래스의 인스턴스에 저장된 메시지를 얻을 수 있다

public class ExceptionEx {
    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);//실행 X
        }catch(ArithmeticException e){
            e.printStackTrace();
            System.out.println("예외메시지 : "+e.getMessage());
        }
        System.out.println(6);
    }
}

ArithmeticException인스턴스의 printStackTrace( )를 사용해서, 호출 스택(call stack)에 대한 정보와 예외 메시지를 출력했다.

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

try{
    ...
}catch(Exception A | ExceptionB e){
    e.printStackTrace();
}

catch블럭을 '|'기호를 이용해서, 하나의 catch블럭으로 합칠 수 있게 되었으며, 이를 '멀티 catch블럭'이라 한다.

예외 발생시키기

키워드 throw를 사용해서 프로그래머가 고의로 예외를 발생시킬 수 있다.

  1. 먼저, 연산자 new를 이용해서 발생시키려는 예외 클래스의 객체를 만든 다음
    • Exception e = new Exception("고의로 발생시킴");
  2. 키워드 throw를 이용해서 예외를 발생시킨다
    • throw e;
public class ExceptionEx10 {
    public static void main(String[] args) {
        try{
            Exception e = new Exception("고의로 발생시킴");
            throw e;
        }catch(Exception e){
            System.out.println("에러 메시지 : "+e.getMessage());
            e.printStackTrace();
        }
        System.out.println("프로그램이 정상 종료당함");
    }
}
public class ExceptionEx11 {
    public static void main(String[] args) {
        throw new RuntimeException();
    }
}

RuntimeException이 발생하여 비정상적으로 종료될 것이다

메서드에 예외 선언하기

메서드에 예외를 선언하려면, 메서드의 선언부에 키워드 throws를 사용해서 메서드 내에서 발생할 수 있는 예외를 적어주기만 하면 된다.

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

메서드 선언부에 예외를 선언함으로써 메서드를 사용하려는 사람이 메서드의 선언부를 보았을 때, 이 메서드를 사용하기 위해서는 어떠한 예외들이 처리되어져야 하는지 쉽게 알 수 있다.
메서드의 예외를 선언할 때 일반적으로 RuntimeException클래스들은 적지 않는다.

public class ExceptionEx12 {
    public static void main(String[] args) throws Exception {
        method1();
    }

    static void method1() throws Exception{
        method2();
    }

    static void method2() throws Exception{
        throw new Exception();
    }
}

finally블럭

finally블럭은 예외의 발생여부에 상관없이 실행되어야할 코드를 포함시킬 목적으로 사용된다

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

예외가 발생한 경우에는 'try->catch->finally'의 순으로 실행되고, 예외가 발생하지 않은 경우에는 'try->finally'의 순으로 실행된다

public class FinallyTest {
    public static void main(String[] args) {
        try {
            startInstall();//프로그램 설치에 필요한 준비를 한다
            copyFiles();//파일들을 복사한다
        }catch(Exception e){
            e.printStackTrace();
            
        }finally{
            deleteTempFiles();//프로그램 설치에 사용된 임시파일들을 삭제한다
        }
    }
    
    static void startInstall(){
        //코드를 작성한다
    }
    static void copyFiles(){
        //파일들을 복사하는 코드를 적는다
    }
    static void deleteTempFiles(){
        //임시파일들을 삭제하는 코드를 적는다
    }
}

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

try-with-resources문이라는 try-catch문의 변형이 새로 추가되었다.

try{
    fis = new FileInputStream("score.dat");
    dis = new DataInputStream(fis);
    ...
}catch(IOException ie){
    ie.printStackTrace();
}finally{
    dis.close();//작업 중 예외가 발생하더라도, dis가 닫히도록 finally블럭에 넣음
}

finally블럭 안에 close( )를 넣었지만, close( )가 예외를 발생시킬 수 있다.

try{
    fis = new FileInputStream("score.dat");
    dis = new DataInputStream(fis);
    ...
}catch(IOException ie){
    ie.printStackTrace();
}finally{
    try{
        if(dis!=null){
            dis.close();
        }
    }catch(IOException ie){
        ie.printStackTrace();
    }
}

try블럭과 finally블럭에서 모두 예외가 발생하면, try블럭의 예외는 무시된다는 것이다.
이러한 점을 개선하기 위해서 try-with-resources문이 추가된 것이다.

       try(FileInputStream fis = new FileInputStream("score.dat");
           DataInputStream dis = new DataInputStream(fis)){
           while (true){
               score = dis.readInt();
               System.out.println(score);
               sum += score;
           }
       }catch(EOFException e){
           System.out.println("점수의 총합은 "+sum+"입니다.");
       }catch(IOException ie){
           ie.printStackTrace();
       }

try-with-resource문의 괄호( )안에 객체를 생성하는 문장을 넣으면, 이 객체는 따로 close( )호출하지 않아도 try블럭을 벗어나는 순간 자동적으로 close( )가 호출된다.

사용자정의 예외 만들기

보통 Exception클래스 또는 RuntimeException클래스로부터 상속받아 클래스를 만들지만, 필요에 따라서 알맞은 예외 클래스를 선택할 수 있다.

class MyException extends Exception{
    MyException(String msg){
        super(msg);
    }
}
class MyException extends Exception{
    private final int ERR_CODE;//생성자를 통해 초기화한다
    
    MyException(String msg, int errCode){
        super(msg);//조상인 Exception클래스의 생성자를 호출한다
        ERR_CODE = errCode;
    }
    
    MyException(String msg){
        this(msg,100);//ERR_CODE를 100(기본값)으로 초기화한다
    }
    
    public int getErrCode(){
        return ERR_CODE;
    }
}

MyException이 발생했을 때, catch블럭에서 getMessage( )와 getErrCode( )를 사용해서 에러코드와 메시지를 모두 얻을 수 있을 것이다

public 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();
        }
    }

    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);
    }
}

MemoryException과 SpaceException, 이 두 개의 사용자정의 예외 클래스를 새로 만들어서 사용했다.

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

한 메서드에서 발생할 수 있는 예외가 여럿인 경우, 몇 개는 try-catch문을 통해서 메서드 내에서 자체적으로 처리하고, 그 나머지는 선언부에 지정하여 호출한 메서에서 처리하도록 함으로써, 양쪽에서 나눠서 처리되도록 할 수 있다.
예외를 처리한 후에 인위적으로 다시 발생시키는 방법을 통해서 가능한데, 이것을 '예외 되던지기(exception re-throwing)'라고 한다.

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;//다시 예외를 발생시킨다
        }
    }
}

연결된 예외(chained exception)

예외 A가 예외 B를 발생시켰다면, A를 B의 '원인 예외(cause exception)'라고 한다.

try{
    startInstall();//SpaceException 발생
    copyFiles();    
}catch(SpaceException e){
    InstallException ie - new InstallException("설치중 예외발생");//예외 생성
    ie.initCause(e);//InstallException의 원인 예외를 SpaceException으로 지정
    throw ie;    
}catch(MemoryException me){
    ...
}

먼저 InstallException을 생성한 후에, initCause( )로 SpaceException을 InstallException의 원인 예외로 등록한다. 그리고 throw로 이 예외를 던진다
initCause( )는 Exception클래스의 조상인 Throwable클래스에 정의되어 있기 때문에 모든 예외에서 사용가능하다

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

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()){    
            throw new RuntimeException(new MemoryException("메모리가 부족합니다."));
        }
}

RuntimeException(Throwable cause) : 원인 예외를 등록하는 생성자

profile
개인공부 블로그입니다. 상업적 용도 X

0개의 댓글