ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 17. 예외처리 (Exception, try-catch ) (1)
    자바(Java) 강의 2019. 5. 2. 12:30

    이 포스트에서는 자바의 예외와 예외 처리 방법의 기본에 대해 알아보도록 한다.

    이전 포스트

    목표

    • 예외란 무엇인가?
    • 예외는 누가 정하는가?
    • 자바 언어가 제공하는 예외는 무엇이 있는가?
    • 예외 처리란 무엇인가?
    • 예외는 어떻게 처리하는가?

    예외란 무엇인가?

    개념적을 봤을 때, 예외란 어떤 이유로 컴퓨터가 더 이상 프로그램을 진행 할 수 없는 상태가 되는것을 의미한다. 예를들어서 배열의 크기보다 큰 곳을 접근하려 한다던가, 0으로 수를 나누려 한다던가, 존재하지 않는 메모리에 접근하려 하는 경우 예외가 발생한다. 

    public class Main {
    public static void main(String[] args) {
    int[] arr = new int[3];
    arr[4] = 3;
    }
    }
    Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 4 at Main.main(Main.java:4)

     위 처럼 크기가 3인 배열에서 인덱스 4에 접근하려 하는경우 ArrayIndexOutOfBoundsException이라는 Exception이 난다. 
     문법적인 측면에서 봤을때 예외란 그냥 오브젝트이다. JVM이 여러분의 프로그램을 실행하다가 예외가 발생하는 경우 그 예외에 맞는 오브젝트(예를들어 이 예제에서는 ArrayIndexOutOfBoundsException 오브젝트)를 만들어 throw라는 문법을 이용해 예외를 발생시킨다. throw에 대해서는 다른 포스트에서 더 자세히 설명하도록 하겠다.

    throw new ArrayIndexOutOfBoundsException();

    예외는 누가 정하는가?

    그렇다면 예외는 누가 정하는가? 첫번째로는 언어 설계자들이 정한다. 자바 언어 설계자들이 어떤 경우에 예외가 발생해야하는지 합의하고 이를 구현한다. 두번째는 여러분과 같은 어플리케이션 개발자들이 정한다. 자바에서는 예외를 상속 가능하게 만들었기 때문에 어플리케이션 개발자들이 Exception클래스를 상속해 여러분의 어플리케이션이 필요한 Exception을 만들 수 있다. 이 포스트에서는 첫번째 경우인 자바에서 이미 제공하는 예외 몇가지에 대해 알아보도록 하겠다. 어플리케이션 개발자들이 정하는 예외는 다음 포스트에서 하도록 한다.

    자바 언어가 제공하는 예외는 무엇이 있는가?

    Exception이라는 클래스가 있다고 했다. Exception이라는 클래스는 java.lang 패키지에 속한다. Exception 클래스는 Throwable이라는 클래스를 상속받는데 이에 대한 얘기는 다른 포스트에서 하겠다. 일단 이 포스트에서는 Exception이라는 클래스가 모든 예외의 수퍼 클래스라는 것을 알아두자. 이 포스트에서 모든 예외를 설명하는것은 힘들다. Exception은 위처럼 상속 가능한 클래스이고, 이를 상속해 무궁무진한 예외를 만들수 있기 때문이다. 따라서 모든 예외를 설명하는 대신에 가장 자주 보게 될 RuntimeException의 NullPointerException을 이용하여 예외를 어떻게 처리하는지 설명하도록 하겠다.


    예외 처리란 무엇인가?

    예외를 처리한다는건 무슨뜻인가? 어떤 메서드에서 예외가 발생했다고 하자. 이 예외는 처리되지 않으면 해당 메서드를 부른 메서드로 전파된다. 처리되지 않은 예외는 계속 메서드를 콜한 메서드로 전파되고 결국 맨 처음 실행되는 메인 메서드로 전파된다. 그리고 메인메서드에서 마저도 예외가 처리되지 않으면 프로그램은 실행을 종료한다. 다시말해 아무도 예외를 처리하지 않으면 프로그램이 종료된다. 

     예외가 처리되지 않아 프로그램이 종료되는 것이 나쁜것은 아니다. 어떤 예외나 에러는 프로그램을 더 이상 실행이 불가능한 상태로 만들고 따라서 프로그램을 종료하는것이 바람직할 수도 있다. 하지만 어떤 때는 이런 예외가 나도 프로그램이 계속 진행할 수 있는 상태일 수 있다. 

    public class StringChecker {
    public boolean endingWithSemicolon(String sentence) {
    return sentence.endsWith(";");
    }
    }

    예를들어 위와 같은 클래스가 있다고 치자. endingWithSemicolon이라는 메서드는 인자로 들어온 sentence가 ;으로 끝나면 true를, 아니면 false를 리턴한다.

    public class Main {
    public static void main(String[] args) {
    String sentenceToCheck = null;
    StringChecker checker = new StringChecker();
    if(checker.endingWithSemicolon(sentenceToCheck)) {
    System.out.println("이 문장은 세미콜론으로 끝난다.");
    } else {
    System.out.println("이 문장은 세미콜론으로 끝나지 않는다.");
    }
    }
    }
    Exception in thread "main" java.lang.NullPointerException at StringChecker.endingWithSemicolon(StringChecker.java:3) at Main.main(Main.java:5)

    이 클래스를 이용해 sentenceToCheck라는 String을 체크하려는데 sentenceToCheck에 null이 들어있어 예외가 나며 종료되었다. 하지만 이게 종료해야 할 만큼 심각한 예외인가? 요구사항에 따라 다르겠지만, null은 세미콜론으로 끝나지 않으므로 그냥 false를 리턴해도 괜찮을 것 같다.

    예외는 어떻게 처리하는가?

    예외는 try {} catch () {}라는 특별한 문법을 이용해 처리한다. 예제를 보자.

    public class StringChecker {
    public boolean endingWithSemicolon(String sentence) {
    try {
    return sentence.endsWith(";");
    } catch (NullPointerException e) {
    System.out.println("Null 문장, false 리턴.");
    return false;
    }
    }
    }
    Null 문장, false 리턴. 이 문장은 세미콜론으로 끝나지 않는다.

    이렇게 코드를 수정한 후 메인을 다시 실행 해 보면 위와 같은 결과 메시지가 출력된다. 

    try {
    // 이 안에서 혹시 예외가 나면 아래의 catch로 들어감.
    } catch (<EXCEPTION_TYPE> e) {
    // Exception을 처리하는 부분
    }

    try catch 구문은 위와 같다. try로 어떤 코드를 감싸고 바로 다음에 catch (<Exception_Type> e) 이렇게 써주면 try 내부에서 난 <Exception_Type>의 예외를 catch 내부에서 처리할 수 있다. <EXCEPTION_TYPE>이란 NullPointerException, ArithmaticException, IndexOutOfBoundsException과 같은 개개의 Exception 클래스를 의미한다. 메서드에서 매개변수를 받는것과 비슷하다고 생각하면 된다. catch (NullPointerException e)이렇게 쓰면 NullPointerException이 발생하는 경우 발생한 NullPointerException 오브젝트가 e라는 변수로 넘어온다는 뜻이다. 

    이제 예외가 발생하는 경우 어떤 코드가 실행되는지 알아보자.

    public class StringChecker {
    public boolean endingWithSemicolon(String sentence) {
    try {
    boolean isEndingWithSemicolon;
    System.out.println("어디까지 실행될까 ? 1");

    System.out.println("어디까지 실행될까 ? 2");
    isEndingWithSemicolon = sentence.endsWith(";");
    System.out.println("어디까지 실행될까 ? 3");

    System.out.println("어디까지 실행될까 ? 4");
    return isEndingWithSemicolon;
    } catch (NullPointerException e) {
    System.out.println("Null 문장, false 리턴.");
    return false;
    }
    }
    } 어디까지 실행될까 ? 1 어디까지 실행될까 ? 2 Null 문장, false 리턴. 이 문장은 세미콜론으로 끝나지 않는다.

    위의 코드는 2까지만 실행되었다. 왜일까?

    프로그램은 예외가 난 시점에서 바로 실행을 중단하고 예외를 던진다(Throw). 이 예외는 catch블록에서 처리되거나 프로그램을 종료시킨다. sentence.endsWith 라인에서 예외가 발생했으므로 '어디까지 실행될까 ? 3'과 4는 실행되지 못하고 바로 catch로 넘어가는 것이다.

    public class StringChecker {
    public boolean endingWithSemicolon(String sentence) {
    try {
    boolean isEndingWithSemicolon;
    System.out.println("어디까지 실행될까 ? 1");

    System.out.println("어디까지 실행될까 ? 2");
    isEndingWithSemicolon = sentence.endsWith(";");
    System.out.println("어디까지 실행될까 ? 3");

    System.out.println("어디까지 실행될까 ? 4");
    return isEndingWithSemicolon;

    } catch (NullPointerException e) {
    System.out.println("Null 문장, false 리턴.");
    }
    System.out.println("여기가 실행될까?");
    return false;
    }
    }
    어디까지 실행될까 ? 1 어디까지 실행될까 ? 2 Null 문장, false 리턴. 여기가 실행될까? 이 문장은 세미콜론으로 끝나지 않는다.

    예외를 처리했다는건 catch문이 끝난후 해당 메서드를 정상적으로 진행할 수 있다는 뜻이다. 비록 try문 안에서 예외가 발생했지만 이는 catch에서 처리되었으므로 그 이후의 메서드를 정상 진행할 수 있다. 따라서 '여기가 실행될까?'가 정상적으로 실행되었다. try-catch를 지우고 실행해보라. '여기가 실행될까?' 부분이 출력되지 않을 것이다. 어디서부터 어디까지 try 블록으로 감쌀것인가는 개발자가 정하면 된다. 

    public class StringChecker {
    public boolean endingWithSemicolon(String sentence) {
    try {
    boolean isEndingWithSemicolon;
    System.out.println("어디까지 실행될까 ? 1");

    System.out.println("어디까지 실행될까 ? 2");
    isEndingWithSemicolon = sentence.endsWith(";");
    System.out.println("어디까지 실행될까 ? 3");

    System.out.println("어디까지 실행될까 ? 4");
    return isEndingWithSemicolon;

    } catch (IndexOutOfBoundsException e) {
    System.out.println("Null 문장, false 리턴.");
    }
    System.out.println("여기가 실행될까?");
    return false;
    }
    }
    어디까지 실행될까 ? 1 어디까지 실행될까 ? 2 Exception in thread "main" java.lang.NullPointerException at StringChecker.endingWithSemicolon(StringChecker.java:8) at Main.main(Main.java:5)

    이번엔 NullPointerException대신 IndexOutOfBoundsException으로 바꿔보자. 예외가 처리되는가? 처리되지 않는다. 왜일까? NullPointerException을 catch하는 부분이 없기 때문이다. 이를 통해 catch에 아무거나 쓴다고 다 예외 처리가 되는것이 아니라 우리가 처리하고 싶은 예외를 지정해 주어야 한다는 사실을 알 수 있다.

    public class StringChecker {
    public boolean endingWithSemicolon(String sentence) {
    try {
    boolean isEndingWithSemicolon;
    System.out.println("어디까지 실행될까 ? 1");

    System.out.println("어디까지 실행될까 ? 2");
    isEndingWithSemicolon = sentence.endsWith(";");
    System.out.println("어디까지 실행될까 ? 3");

    System.out.println("어디까지 실행될까 ? 4");
    return isEndingWithSemicolon;

    } catch (Exception e) {
    System.out.println("Null 문장, false 리턴.");
    }
    System.out.println("여기가 실행될까?");
    return false;
    }
    }
    어디까지 실행될까 ? 1 어디까지 실행될까 ? 2 Null 문장, false 리턴. 여기가 실행될까? 이 문장은 세미콜론으로 끝나지 않는다.

    그렇다면 위는 왜 실행될까? 위는 NullPointerException이 아니라 Exception인데 말이다. 그 이유는 NullPointerException이 Exception을 상속하기 때문이다. 

    Animal animal = new Cat();

    이전 포스트에서 자바 상속에 대해 설명할 때, 수퍼 클래스의 형인 변수에 서브클래스의 오브젝트를 생성해서 할당 할 수 있었다. 마찬가지로 메서드나 catch문의 매개변수도 비록 Exception이라는 수퍼클래스 형의 변수지만 서브클래스의 오브젝트가 넘어올 수 있다. (<- 이런걸 다형성이라고 한다.) Exception으로 선언된 Exception e은 IndexOutOfBoundsException, NullPointerException, UnsupportedOperationException등 Exception을 상속하는 예외라면 뭐든 catch할 것이다. 

    public class StringChecker {
    public boolean endingWithSemicolon(String sentence) {
    try {
    boolean isEndingWithSemicolon;
    System.out.println("어디까지 실행될까 ? 1");

    System.out.println("어디까지 실행될까 ? 2");
    isEndingWithSemicolon = sentence.endsWith(";");
    System.out.println("어디까지 실행될까 ? 3");

    System.out.println("어디까지 실행될까 ? 4");
    return isEndingWithSemicolon;

    } catch (ArithmeticException e) {
    System.out.println("ArithmeticException");
    } catch (IndexOutOfBoundsException e) {
    System.out.println("IndexOutOfBoundsException");
    } catch (NullPointerException e) {
    System.out.println("NullPointerException");
    }
    System.out.println("여기가 실행될까?");
    return false;
    }
    } 어디까지 실행될까 ? 1 어디까지 실행될까 ? 2 NullPointerException 여기가 실행될까? 이 문장은 세미콜론으로 끝나지 않는다.

    위처럼 예외를 여러개 catch할 수 있다. 어떤 예외가 나는가에 따라서 다른 처리를 해 주어야 할 수 있기 때문이다. 예외 오브젝트에 따라 해당 예외를 캐치하는 캐치 구문으로 들어간다. 

    이번 포스트에서는 예외 처리의 기본에 대해 설명했다. 다음 포스트에서는 try-catch-finally 구문에 대해 설명하고, 예외가 발생 할 경우 어떤 코드가 실행되고 실행되지 않는지에 대해 더 자세하게 알아보도록 하겠다.

    연습문제

    ArithmeticException이란 무엇인가? ArithmeticException을 일부러 내 보고 catch해보아라. 꼭 ArithmeticException일 필요는 없다. 자바에 어떤 다른 예외가 있나 찾아보아라. 

    다음 포스트: 17. 예외처리 (Exception, try-catch-finally) (2)

    댓글

f.software engineer @ All Right Reserved