본문 바로가기
프로그래밍/Java

[java] exception 처리하기 - try-catch-with-resources

by 사바라다 2020. 4. 26.

안녕하세요. 오늘은 java7에서 추가된 exception을 좀 더 쉽게 처리할 수 있는 방법에 대해서 알아보는 시간을 가지도록 하겠습니다.

우리가 일반적으로 파일을 읽고 쓰는데 Stream을 이용합니다. Stream은 단방향으로 데이터를 전송할 수 있는 개념입니다. 일반적으로 FileInputStream Class를 이용하면 파일에 있는 데이터를 읽을 수 있습니다. 이때 InputStream을 연 후 파일을 읽고 마무리 후에는 Stream을 닫는 흐름을 일반적으로 가져가게 됩니다. 이런 흐름에서 exception이 발생할 수 있는 위치는 InputStream을 열때, 파일을 읽을 때, InputStream을 닫을 때 총 3군대입니다. 이럴때 exception 처리를 어떻게 할 수 있을지 다양하게 보도록 하겠습니다.

try-catch를 이용한 처리

가장 먼저 try와 catch만을 이용해 exception을 처리해보도록 하겠습니다.

private void exceptionTryCatch(String fileName) {
        File file = new File(fileName);
        FileInputStream fileInputStream = null;
        try {
            fileInputStream = new FileInputStream(file);
            int read = fileInputStream.read();// 데이터 1byte 읽기
            try {
                fileInputStream.close();
            } catch (IOException e) {
                // 1. 파일을 열어 정상적으로 읽었으나 Stream을 닫을 때 에러 발생의 경우
            }
        } catch (FileNotFoundException e) {
            // 2. 파일을 찾지 못했을 경우
        } catch (IOException e) {
            // 3. 파일을 찾았으나 읽지 못했을 경우
            try {
                if(fileInputStream != null) {
                    fileInputStream.close();
                }
            } catch (IOException e2) {
                // 4. 파일을 찾았은 읽지 못하여 fileStream을 닫으려고 했으나 닫지 못했을 경우
            }
        }
    }

코드가 상당히 난잡해 보이는 것을 알 수 있습니디. 예외가 상당히 다양한 곳에서 발생하기 때문에 그에 대한 처리를 별도로 처리해 주고 있습니다.. 먼저 파일의 Stream을 열때 예외가 발생할 수 있습니다. 파일이 존재하지 않으면 FileNotFoundException이 발생합니다. 또한 코드에 기술되어 있지는 않지만 입력받은 fileName 파라미터가 Null일 경우는 NullPointerException이 발생할 가능성도 있습니다. 파일을 정상적으로 찾았다면 이제 File을 읽어야합니다. File을 정상적으로 읽지 못했다면 IOException이 발생할 것입니다. File을 정상적으로 읽은 여부와 상관없이 InputStream을 열었으므로 닫아주어야합니다. 따라서 정상적으로 읽었을때의 경우와 정상적으로 읽지 못했을 경우 2경우 모두에 대해서 inputStream을 닫아주어야 하며 이때 발생할 수 있는 예외에 대해서 다시한번 처리를 해 주어야 합니다.

try-catch-finally를 이용한 처리

위 코드를 finally 블럭을 이용해서 위의 코드를 좀 더 간단히 해보도록 하겠습니다. finally는 try-catch 블럭 내에서 exception의 발생 유무와 상관없이 마지막에 무조건 처리하는 코드 블럭입니다.

private String exceptionTryCatchFinally(String hello) {
    File file = new File(hello);
    FileInputStream fileInputStream = null;
    try {
        fileInputStream = new FileInputStream(file);
        int read = fileInputStream.read();// 데이터 1byte 읽기
        System.out.println("try block");
        return "method";
    } catch (FileNotFoundException e) {
        System.out.println("catch block");
        return "file not found";
    } catch (IOException e) {
        System.out.println("catch block"); 
        return "IOException";
    } finally {
        System.out.println("finally block");
        try {
            if(fileInputStream != null) {
                fileInputStream.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

try, catch만을 이용했던 코드보다 깔끔해 진것을 확인할 수 있습니다. 모든 작업이 마무리 된 후 try, catch만 사용했을 때 InputStream을 닫아주는 try의 마무리 작업과 catch의 마무리 작업을 finally 블럭에서 공통화 하여 작업할 수 있게 되었습니다.

추가적으로 finally 블럭은 try 블럭 또는 catch 블럭이 마무리 되고 실행되게 됩니다. 따라서 위의 메서드가 정상적으로 처리되 었다고 가정하면 아래와 같이 출력됩니다.

try block // try blcok 내
finally block // finally block 내
method // 메서드를 호출한 메서드

try-catch-with-resource를 이용한 처리

try-catch-with-resource는 java7에서 추가된 방법입니다. try-catch-finally를 이용한다고 하더라도 최소한 2개의 try-catch 블럭이 필요합니다. 이러한 부분을 try-catch-with-resource를 이용하면 하나의 try-catch 블럭으로 마무리할 수 있도록 하는 방법입니다.

try-catch-with-resource는 try (resource) - catch 문법을 이용해게 되는데 resource에는 객체가 들어가게됩니다. 이 객체는 Closeable 인터페이스를 반드시 상속받아야 합니다.

private void exceptionTryCatchWithResource(String hello) {
    File file = new File(hello);
    try (FileInputStream fileInputStream = new FileInputStream(file)){
        int read = fileInputStream.read();// 데이터 1byte 읽기
        System.out.println("try block");
    } catch (FileNotFoundException e) {
        System.out.println("catch block");
    } catch (IOException e) {
        System.out.println("catch block");
    }
}

위 코드는 FileInputStream을 try-catch-with-resource를 이용해 구현한 것입니다. finally에 들어가 있어야할 fileInputStream#close 메서드가 제거된 것을 확인할 수 있습니다.

FileInputStream은 BufferedInputStream과 함께 데코레이터 패턴을 사용하여 부가적인 기능을 추가할 수 있습니다. 이때 BufferedInputStream과 FileINputStream을 모두 닫아주어야 합니다. finally 코드로 이용하면 아래와 같이 표현됩니다.

finally {
    if(bufferedInputStream != null) {
        try {
            bufferedInputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    try {
        if(fileInputStream == null) {
            fileInputStream.close();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

하지만 try-catch-with-resource에서는 아래와 같이 표현하면 동일한 효과를 가져올 수 있습니다.

try (BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(file))){
    ...
} catch ( ) {
    ...
}

마무리

오늘은 try, catch를 이용한 exception 처리, try, catch, finally를 이용한 exception 처리, 마지막으로 try-catch-with-resource에 대해서 알아보는 시간을 가졌습니다.

감사합니다.

참조

https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html

댓글