경주장

9주차 과제: 예외 처리 #9 자바에서 예외 처리 방법 (~03.09) 본문

JAVA/whiteship_live-study

9주차 과제: 예외 처리 #9 자바에서 예외 처리 방법 (~03.09)

달리는치타 2022. 3. 3. 13:23

✔️ 학습할 것 (필수)

+ 예외란?

  • 자바에서 예외 처리 방법 (try, catch, throw, throws, finally)
  • 자바가 제공하는 예외 계층 구조
  • Exception과 Error의 차이는?
  • RuntimeException과 RE가 아닌 것의 차이는?
  • 커스텀한 예외 만드는 방법

자바에서 예외 처리 방법

// Note: This class will not compile yet.
import java.io.*;
import java.util.List;
import java.util.ArrayList;

public class ListOfNumbers {

    private List<Integer> list;
    private static final int SIZE = 10;

    public ListOfNumbers () {
        list = new ArrayList<Integer>(SIZE);
        for (int i = 0; i < SIZE; i++) {
            list.add(new Integer(i));
        }
    }

    public void writeList() {
        // The FileWriter constructor throws IOException, which must be caught.
        PrintWriter out = new PrintWriter(new FileWriter("OutFile.txt"));

        for (int i = 0; i < SIZE; i++) {
            // The get(int) method throws IndexOutOfBoundsException, which must be caught.
            out.println("Value at: " + i + " = " + list.get(i));
        }
        out.close();
    }
}

 

Checked Exception인 IOException을 던지는 FileWriter의 생성자

위의 코드는 컴파일 되지 않습니다. 그이유는 FileWirter가 던지는 Checked Eception인 IOException을 처리하지 않았기 때문입니다. 그대로 컴파일 하게 되면 아래와 같은 컴파일 에러가 발생합니다.

java: unreported exception java.io.IOException; must be caught or declared to be thrown

 

예외 처리에 관한 지식이 없으면 간단한 파일 읽기 쓰기 프로그램 조차 컴파일 할 수 없습니다. 자바에서 예외처리 방법을 배워봅시다!


try, catch, finally 등의 키워드를 통해 예외를 처리하는 방법을 알아보겠습니다.


Exception Handler를 만드는 가장 첫번째 step은 try 블럭을 작성하는 것입니다. 

try {
  code
}
catch and finally blocks ...

try 블락의 코드에는 예외가 발생할 수 있는 코드를 작성합니다.

위의 FileWriter 생성자 예시에서 컴파일 에러를 없애는 방법 중 하나는 FileWriter의 생성자 부분을 try 구문에 넣는 것입니다.

 

가장 단순한 방식은 아래와 같습니다.

public void writeList() {
        PrintWriter out = null;
        try {
            out = new PrintWriter(new FileWriter("OutFile.txt"));
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        for (int i = 0; i < SIZE; i++) {
            out.println("Value at: " + i + " = " + list.get(i));
        }
        out.close();
}

예외가 발생할 수 있는 line을 try 블럭에 넣었고 컴파일 에러를 해결하였습니다. 하지만 위 코드는 여전히 문제가 있습니다. FileWriter 생성에 IOException이 생겨 예외가 발생하였다면 PrintWriter인 out은 초기화 되지 않습니다. 따라서 예외처리 구문 아래 for-loop에서 NullPointException이 발생합니다.

 

이를 해결한 코드는 아래와 같습니다.

public void writeList() {
    PrintWriter out = null;
    try {
        System.out.println("Entered try statement");
        out = new PrintWriter(new FileWriter("OutFile.txt"));
        for (int i = 0; i < SIZE; i++) {
            out.println("Value at: " + i + " = " + list.get(i));
        }
    }
    catch and finally blocks  . . .
}

out의 생성과 그를 활용한 코드인 for-loop 구문까지 모두 try-block에 넣음으로써 NullPointException의 위험을 없앴습니다.


try-block에 해당되는 Exception Handler는 catch-block을 통해 만들 수 있습니다. catch-block은 try-block 바로 뒤에 작성하며 여러개 작성 될 수 있습니다.

try {

} catch (ExceptionType name) {

} catch (ExceptionType name) {

}

ExceptionType은 Throwable 클래스를 상속받은 클래스여야 하며 Exception Handler인 catch block 내부에서는 try 구문에서 던진 ExceptionType 예외를 name이름으로 사용할 수 있습니다.

Catch Block이 여러개 작성된 다중 catch의 경우 catch block의 작성 순서가 중요합니다. 다중 catch의 경우 위에서 부터 던저진 예외타입을 비교하며 예외 처리를 지원하는지 확인합니다. catch block은 선언된 예외 타입의 자식 타입까지도 모두 잡아버립니다. 때문에 부모 타입의 예외와 자식 타입의 예외를 동시에 잡는 try-catch 구문을 작성 할 경우 항상 구체적인 자식의 타입을 먼저 작성해야합니다. 
Exception Handler (캐치 블락)은 단순히 예외 메시지를 출력하고 프로그램을 멈추는 것 외에도 예외를 해결하거나 사용자에게 예외 해결을 요청하거나 예외를 떠넘기는 다양한 기능을 제공할 수 있습니다.

여러개의 예외에 대해서 같은 방식의 예외처리를 제공하고 싶은 경우 multi 캐치 문법을 적용할 수 있습니다.

catch (IOException|SQLException ex) {
    logger.log(ex);
    throw ex;
}

finally 블럭은 try 블럭이 있다면 항상 실행됩니다. finally 블럭은 예외가 catch 블럭에 의해서 잡히든 말든 심지어 try나 catch 블럭에서 return, continue, break등의 분기문으로 블럭을 탈출하더라도 실행됩니다. 이런 특징 때문에 finally 블럭은 주로 clean-up code/ resource close등에 활용됩니다. resource close의 용도로 finally구문을 활용한다면 try-with-resource 문법을 활용하는 것이 좋습니다.


try-with-resource

자바 라이브러리에는 close 메서드를 호출해 직접 닫아줘야 하는 자원이 많습니다. InputStream, OutputStream, java.sql.Connection 등이 있습니다.

 

try-with-resource 문법은 자원의 close를 무조건적인 호출을 기존의 try-finally에 비해 간단한 문법으로 제공합니다.

InputStream과 FileStream을 동시에 활용하는 copy 메소드를 두 방식으로 작성하면 아래와 같습니다.

 

static void copy1(String src, String dst) throws IOException {
    InputStream in = new FileInputStream(src);
    try{
        OutputStream out = new FileOutputStream(dst);
        try{
            byte[] buf = new byte[BUFFER_SIZE];
            int n;
            while((n = in.read(buf)) >= 0)
                out.write(buf, 0, n);
        } finally {
            out.close();
        }
    } finally {
        in.close();
    }
}
static void copy2(String src, String dst) throws IOException {
    try (InputStream in = new FileInputStream(src);
        OutputStream out = new FileOutputStream(dst)) {
        byte[] buf = new byte[BUFFER_SIZE];
        int n;
        while ((n = in.read(buf)) >= 0)
            out.write(buf, 0, n);

    }
}

copy1에 비해 copy2는 코드가 더 짧고 분명해집니다.

 

@출처)

 

Catching and Handling Exceptions (The Java™ Tutorials > Essential Java Classes > Exceptions)

The Java Tutorials have been written for JDK 8. Examples and practices described in this page don't take advantage of improvements introduced in later releases and might use technology no longer available. See Java Language Changes for a summary of updated

docs.oracle.com

그리고 

Effective Java 3/e 아이템 9 - try-finally 보다는 try-with-resource를 사용하라