자바 스터디 8회차 준비

5 minute read

아래는 7월 7일 진행 할 스터디 준비 내용이다.

  • 8주차 목차
    • 예외처리
      • 프로그램 에러
      • 에러 클래스
      • Exception, RuntimeException
      • try-catch
      • printStackTrace(), getMessage()
      • 멀티 catch블럭
      • 예외 발생시키기
      • 메서드에 예외 선언하기
      • finally
      • 사용자 정의 예외 만들기
      • try-with-resource

1. 예외처리

1-1) 프로그램 에러

프로그램이 실행 중 어떠한 원인으로 인해 오작동하거나, 비정상적으로 종료되는 경우가 있다.
이때 원인이 되는 것을 오류(error)라 부른다.

이 에러는 크게 두 가지로 나눠진다.

  1. Compile Time Error
  2. Runtime Error

두 가지로 나눠지는 기준은 바로 발생 시점이다.

컴파일 에러 같은 경우 말 그대로 컴파일 시 (javac 명령어를 다시 기억하자.)
런타임 에러는 런타임 시 (프로그램 실행 도중) 발생하는 에러이다.

일반적으로 컴파일 에러는 소스코드에 대한 오탈자, 문법이 틀렸거나, 알맞지 않은 자료형을 사용했을 때 등
프로그램이 실행하기 전 컴파일러에 의해 발생하는 에러이다.

IDE를 사용하면 보통 빨간색 밑줄이 그어지는 에러가 바로 컴파일 에러이다.

그에반해 런타임 에러는 실행 도중에 해당 구문을 실행하면서 발생하는 에러이다.
예를들어 배열의 존재하지 않는 index를 호출할 경우 ArrayIndexOutOfBoundsException,
숫자를 0으로 나눌 때 발생하는 ArithmeticException 등이 있다.

런타임 에러의 경우 다시 두 가지로 나뉘어진다.

  1. Compile Time Error
  2. Runtime Error
    1. Error
    2. Exception(예외)

에러는 StackOverflowError와 같은 코드를 통해 복구 할 수 없는 심각한 오류이며,
예외는 코드를 통해 복구 할 수 있는 에러이다.


1-2) 에러 클래스

자바는 에러 또한 클래스로 관리된다.
에러 클래스의 최상위 조상도 마찬가지로 Object이다.

error


1-3) Exception, RuntimeException

Exception은 보통 외부의 영향으로 발생한다.
예를들어 회원가입 시 전화번호를 입력해야하는데, 문자열로 입력했을 경우 등이다.

RuntimeException은 프로그래머의 실수로 인해 발생하는 예외이다.
값이 null인 참조변수를 호출할 경우 발생하는 NullPointerException 등이다.


1-4) try-catch

예외가 발생할 부분을 미리 알 수 있다면 try-catch 문법을 사용해 대비를 할 수 있다.
이를 예외처리라 한다.

먼저 사용법은 try 블럭에 예외가 발생할 수 있는 문장을 넣는다.
그리고 catch 블럭에 예외가 발생했을때 처리할 문장을 넣는다.

예시를 통해 알아보자.

public class SignUpClass {

    public static void main(String[] args) {
        int phoneNo = phoneNoChecked();
        System.out.println(phoneNo);
    }

    static int phoneNoChecked() {
        Scanner sc = new Scanner(System.in);

        System.out.print("전화번호를 입력해주세요\n");

        try {
            int phoneNo = sc.nextInt();
            return phoneNo;
        } catch (InputMismatchException e) {
            System.out.println("숫자만 입력 가능합니다.");
            return phoneNoChecked();
        }
    }

}

간단한 전화번호 체크 메서드이다.
만약 정상적인 숫자로 입력했을 경우 phoneNo 변수에 값이 정상적으로 저장되겠지만,
사용자의 실수로 문자가 입력됬다면 InputMismatchException이 발생한다.
이때 catch 블럭이 예외를 잡고, 다시 전화번호를 입력할 수 있도록 처리를 했다.

이처럼 예외 발생 이후 해당 Exception에 맞는 catch문을 만나면,
예외가 처리된 후 다음 문장을 다시 이어나간다.

그러나 만약 발생한 Exception과 일치하는 catch 블럭이 없다면 예외는 처리되지 않는다.

또한 try 블럭에서 예외가 발생하지 않는다면, catch 블럭을 무시하고 진행한다.

예외는 상속구조로 되어있기 때문에, catch문에 특정 Exception이 아닌 그냥 Exception을 넣는다면 모든 예외를 catch 블럭이 잡을 수 있게 된다.

예외가 발생했을 때 catch 블럭은 발생한 예외와 catch문의 예외를 instanceof를 통해 true를 반환할 때 까지 검사를 하게 된다.

사실 이 과정의 비용이 굉장히 비싼편이므로, 참고하도록 한다.


1-5) printStackTrace(), getMessage()

예외가 발생했을 때 예외 클래스의 인스턴스에는 해당 예외에 대한 정보가 담겨있다.

printStackTrace()를 통해 예외발생 시 호출 스택에 있던 메서드 정보와 예외 메시지를 화면에 출력한다.

getMessage()를 통해 저장된 메시지를 얻을 수 있다.

어느 부분에서 예외가 발생했는지 쉽게 알 수 있는 메서드이니 개발 시 유용하게 사용할 수 있다.

그러나 실무에서는 보안적인 측면 때문에 잘 사용하지 않는 편이다.


1-6) 멀티 catch블럭

JDK1.7 부터 추가된 기능으로 catch블럭을 ‘|’ 기호를 통해 하나의 catch 블럭으로 합칠 수 있다.

try {
    ...
} catch(InputMismatchException | NullPointerException e) {
    ...
}

이때 주의할 점은 catch문의 예외가 상속관계라면 굳이 두개를 쓸 필요가 없이,
부모 타입의 예외만 작성해도 괜찮기 때문에 컴파일에러가 발생한다.


1-7) 예외 발생시키기

상황에 따라 고의적으로 예외를 발생시켜야 하는 경우가 있다.

try {
    Exception e = new Exception("예외발생!");
    throw e;
    // throw new Exception("예외발생!"); 으로 줄일 수 있다.  
} catch(Exception e) {
    ...
}

이때 생성자로 사용된 예외발생! 이라는 문자열은 이후 catch 블럭에서
e.getMessage()를 통해 꺼낼 수 있다.


1-8) 메서드에 예외 선언하기

예외처리에는 try-catch문을 사용하는 것 외에도
메서드에 예외를 선언하는 방법이 있다.

바로 throws 라는 키워드를 사용하는 방법이다.

public void method() throws NullPointerException, IOException {

}

이러한 경우는 상당히 많이 사용되는 방식이다.

우선 라이브러리의 메서드, 혹은 유틸 메서드 경우 많은 클래스에서 호출될 가능성이 높다.
그러한 메서드를 미리 예외처리를 한다면, 개발자의 자유도가 상당히 낮아질 수 있다.

public static void main(String[] args) {
    System.out.println(parseInt("a"));
}

static int parseInt(String str) {
    try {
        return Integer.parseInt(str);
    } catch (NumberFormatException e) {
        return 0;
    }
}

숫자 문자열을 int 타입으로 변환해주는 메서드이다.
만약 이게 라이브러리라고 생각해보자.

라이브러리를 개발한 개발자는 형변환에 실패했을 때, int의 기본값인 0을 리턴해주는 것이 옳다고 생각했다.

그러나 라이브러리를 통해 개발 하는 개발자는 Exception이 발생해줘야 변환에 실패했는지 알 수 있을 것이다.

이런 경우 보통 메서드에 예외를 선언해 호출한 메서드에게 예외를 위임한다.

또한 메서드에 예외가 선언되어있기 때문에, 이 메서드에서 해당 예외가 발생할 가능성이 있다는 것을 명시적으로 나타낼 수 있다.

public static void main(String[] args) {
    try {
        System.out.println(parseInt("a"));
    } catch (NumberFormatException e) {
        e.printStackTrace();
    }
}

static int parseInt(String str) throws NumberFormatException {    
    return Integer.parseInt(str);
}

예외를 발생시키는 throw와 매우 비슷하기 때문에 혼동하지 않도록 주의한다.


1-9) finally

finally 블럭은 예외 발생여부와 관련없이 무조건 실행해야하는 블럭이다.
필수적으로 사용해야하는 것은 아니다.

만일 예외가 발생하지 않았다면 try-finally가 실행되며,
예외가 발생했다면 try-catch-finally가 실행된다.

public static void main(String[] args) {
    try {
        throw new Exception("에러발생 코드!");
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        System.out.println("무조건 실행!");
    }
}

1-10) 사용자 정의 예외 만들기

때때로는 예외를 직접 만들어서 사용할 수 있다.
이 경우 Exception 혹은 RuntimeException 클래스를 상속받는 클래스를 만들어 사용할 수 있다.

public class MyException extends Exception {

    public MyException(String message) {
        super(message);
    }

}

public class Main {
    public static void main(String[] args) {
        try {
            throw new MyException("에러 발생!");
        } catch (Exception e) {
            e.printStackTrace();
        } 
    }
}

1-11) try-with-resource

JDK1.7 부터 추가된 기능이다.
자바는 Garbage Collector에 의해 자원관리가 되지만,
외부 자원은 직접 닫아줘야한다.

하지만 그러기엔 코드가 굉장히 지저분해질 수 있다.
이때 사용할 수 있는 문법이다.

public class SaveFile {
    public static void main(String args[]) {
        try {
            String filePath = "/Users/ekkkk1/workspace/java-study/text.txt";
            File file = new File(filePath);

            if (!file.exists()) {
                file.createNewFile();
            }
            BufferedWriter writer = new BufferedWriter(new FileWriter(file));

            writer.write("Hello");
            writer.newLine();
            writer.write("World!");
            writer.newLine();
            writer.flush();
            writer.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

이러한 코드를 아래처럼 변경할 수 있다.

public class SaveFile {
    public static void main(String args[]) {
        String filePath = "/Users/ekkkk1/workspace/java-study/text.txt";
        File file = new File(filePath);

        try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {

            if (!file.exists()) {
                file.createNewFile();
            }

            writer.write("Hello");
            writer.newLine();
            writer.write("World!");
            writer.newLine();
            writer.flush();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

큰 차이가 없다고 느낄 수 있겠지만,
코드를 읽는 부분에서도 코드가 줄었기 때문에 훨씬 간결하고,
사용하는 입장에서도 실수를 줄일 수 있기 때문에 외부 자원을 사용하는 경우라면
try-with-resource 문법을 사용해보도록 한다.

만약 여러 객체의 자원을 사용한다면, 세미콜론을 구분자로 사용해 여러개의 객체를 구문에 담을 수 있다.


Leave a comment