Java의 정석 - 예외

원태연·2022년 5월 30일
0

Java의 정석

목록 보기
11/19
post-thumbnail

예외

프로그램의 오류에는 크게 3가지 종류가 있다

  1. 컴파일 에러 : 컴파일 시 발생
  2. 런타임 에러 : 실행 시 발생
  3. 논리적 에러 : 문제없이 작동하지만 의도와 다르게 작동한는 것
public class playGround {
    public static void play() {
        int[] intArr = {1, 2, 3, 4};
//      system.out.println(intArr[0]);  컴파일 에러 system은 컴파일 시 체크 가능
//      System.out.println(intArr[8]);  intArr[8]이라는 구문에는 오류가 없지만 실행 중 오류가 발생
        System.out.println("First index = " + intArr[1]); //의도와 다른 출력
    }
}

컴파일 에러의 경우는 컴파일러가 기본적으로 오타나 잘못된 구문, 자료형의 체크 등 기본적으로 체크를 한다. IDE에서도 충분한 체크를 통해 오류를 줄일 수 있다.

하지만, 정상적으로 컴파일이 되더라도 에러가 발생할 수 있다. 이러한 잠재적인 에러는 컴파일러가 거르지 못하고, 프로그램이 비정상적으로 종료된다.

이런 에러를 방지하기 위해 런타임시 발생할 수 있는 경우에 대비하여 처리하여야 한다.

Java에서는 런타임에러를 에러예외로 구분하였다. 이 둘의 차이는 프로그램 코드를 통해 수습할 수 있느냐 없느냐의 차이이다.

런타임 오류의 계층구조

Java에서는 런타임오류들을 클래스로 정의하였다. 오류들은 모두 클래스이고, 최고 조상은 Object()이다. 그 밑으로는,

Throwable

Error

StackOverFlow, OutOfMemoryError, ...

Exception

ClassNotFoundException, ...

RuntimeException

ClassCastExecption, NullPointerException, ...

ErrorException의 차이는 코드를 통한 수습의 가능성이다.

ExceptionRuntimeException이냐 아니냐로 구분지을 수 있는데, 그 차이는 예외의 발생요인이 프로그래머인가 외부인가이다. 프로그래머의 실수로 발생하는 예외는 RuntimeException로 구분 짓는다.

또, 예외처리의 의무에서도 차이점이 있다.

Exception과 그 자손에 해당하는 클래스들은 예외처리를 해주지 않으면, 컴파일 조차 안된다.

하지만, RuntimeException과 그 자손에 해당하는 클래스들은 예외처리를 하지 않아도 컴파일이 된다. 그렇다고 해서 런타임에러가 발생하지 않는 다는 것은 아니다.

발생할 수 있는 모든 예외에 대해 처리가 강요되는 것은 안정성이 더 보장될 수 있지만, 불필요한 예외처리를 강요받아 효율이 낮아질 수 있기 때문에, 자주 사용되는 경우와 연관되어있는 예외들의 처리들은 의무가 아닌 선택사항으로 남아져 있는 것이다.

int[] a = new int[10]; //NullPointerException의 가능성이 있지만 처리의 의무는 아님

예외의 처리

try-catch

이러한 예외를 어떻게 수정을하고 처리하는지 알아보자.

예외의 생성

int a = 10 / 0; //--1
/*
Exception in thread "main" java.lang.ArithmeticException: / by zero
	at playground.playGround.play(playGround.java:6)
	at playground.main.main(main.java:6)
*/

런타임 에러가 발생하면, 어떤 에러이고 어디서 발생하는지 알려준다.

에러가 발생하면, 그에 해당하는 에러(예외)클래스 타입의 인스턴스가 생성된다. 그 인스턴스에는 에러에 대한 정보와 메서드를 가지고 있기 때문에 예외가 발생한다고 해서 무조건 종료되는 것은 아니다.

try-catch

try-catch를 통해 에러를 처리할 수 있다.

public class playGround {
    public static void play() {
        try{
            System.out.println(1+1);
            System.out.println(1/0);  //--1
          	System.out.println(1+2);  //--3
        }catch (ArithmeticException e){ //--2
            System.out.println("ArithmeticException");
        }
    }
}
// result
// 2
// ArithmeticException

--1을 보면, ArithmeticException 타입의 인스턴스가 생성된다. 이때 인스턴스가 생성된 문장이 try블럭안에 있다면, 이를 처리할 수 있는 catch블럭을 찾아간다.

--2에서 ArithmeticException를 처리하고 있기 때문에 해당 구현부를 수행하고, try-catch문을 빠져나온다. 그래서 --3의 문장은 실행되지 않는다.

public class playGround {
    public static void play() {
        try{
            System.out.println(1+1);
            System.out.println(1/0);
            System.out.println(1+2);
        }
        catch (Exception e){ //--1
            System.out.println("Exception");
        }catch (ArithmeticException e){ //--2
            System.out.println("ArithmeticException");
        }
    }
}

catch문도 주의해야할 점이 있다. --1Exception타입은 --2ArithmeticException의 부모이다. 그래서, try문에서 발생한 에러는 --1에서도 잡아낼 수 있다.(다형성)

try-catch문의 흐름을 이해했다면, 더 넓은 범위의 예외타입이 아래쪽에 위치해야 좀 더 세부적인 예외처리가 가능하다는 것을 쉽게 알 수 있다.

finally

public class playGround {
    public static void play() {
        try{
            System.out.println(1/0);
        }catch (Exception e){ //--1
            System.out.println("Exception");
        }finally{
            System.out.println("finished");
        }
    }
}

예외의 발생여부와 상관없이 수행하고자 하는 문장은 finally블럭을 통해 실행 할 수 있다.

예외 클래스의 메서드

public class playGround {
    public static void play() {
        try{
            System.out.println(1+1);
            System.out.println(1/0);  
            System.out.println(1+2);
        }
        catch (ArithmeticException e){ 
            e.getMessage();
            e.printStackTrace();
        }
    }
}

예외클래스들은 여러 메서드를 가지고 있기 때문에, 이를 활용해서 생성된 예외 인스턴스를 다양하게 다룰 수 있다.

예외 발생시키기

public class playGround {
    public static void play() {
        try{
            throw new ArithmeticException("ArithmeticException!!");
        } catch (ArithmeticException e){
            System.out.println(e.getMessage());  //ArithmeticException!!
        }
    }
}

throw를 통해 예외를 고의적으로 발생 시킬 수 있다.

throw new Exception()에서 매개변수는 getMessage()메소드와 연관있다는 것을 알 수 있다.

멀티 catch블럭

try{
   System.out.println(1/0);
 }
catch (ArithmeticException e){ 
  System.out.println("Error");
}
catch (NullPointerException e){ 
  System.out.println("Error");
}

catch문의 중복을 제거할 수 있다.

try{
   System.out.println(1/0);
 }
catch (ArithmeticException | NullPointerException e){ 
  System.out.println("Error");
  //e.methodOnlyFromNullPointerEx(); ERROR  --1
}

두 코드는 동일한 코드이다. 이때 주의해야 할 점은,

  • 다른 타입의 참조변수임을 고려하여 구현해야 한다는 점, --1
  • 부모 자식간의 멀티 블럭은 허용되지 않는다는 점이다.

예외 반환하기

try-catch문 말고도 예외를 처리하는 방법이 있다.

메소드에서 예외 타입을 반환하여 던지는 경우가 있다.

public class playGround {
  public static void play() {
    int a = 0;
    try{
      checkNum(a);
      System.out.println(100 / a);
    }catch (ArithmeticException e){
      System.out.println(e.getMessage());
    }
  }
  static void checkNum(int a) throws ArithmeticException{
    if (a == 0){
      throw new ArithmeticException("Can't be divided by 0");
    }
    System.out.println("Continue");
  }
}
// Can't be divided by 0

checkNum()메소드가 int a의 값에 따라 예외처리를 하는 것을 볼 수 있다.

이때,

void checkNum(int a) throws ArithmeticException {}

와 같은 구문으로 반환하려는 예외 타입을 명시할 수 있다. 여러개를 명시 할 수 있다.

주의할 점은, 이 메소드를 오버라이딩하는 경우이다. 오버라이딩의 경우 조상보다 많은 예외선언은 불가하기 때문에,

class Child extends Parent{
  void checkNum(int a, int b) throws Exception{}; //Override 불가
}

와 같은 결과가 나타날 수 있다. Exception은 모든 예외타입의 부모이기 때문에, 선언된 갯수는 하나이지만 실제 반환할 수 있는 예외가 많다는 점을 주의하자.

사용자 정의 예외

Java에서 제공하는 예외타입 이외의 예외들을 선언할 수 있다.

class OverNumberException extends Exception{ //Exception을 상속받았기 때문에 반드시 예외처리 필요
    OverNumberException(String errMessage) {
        super(errMessage);
    }
}
public class playGround {
    public static void play() {
        int a = 1000;
        try{
            checkNum(a);
            System.out.println(100 / a);
        }catch (OverNumberException e){
            System.out.println(e.getMessage());
        }

    }
    static void checkNum(int a) throws OverNumberException {
        if (a > 100){
            throw new OverNumberException("Over Value"); //--1
        }
        System.out.println("Continue");
    }
}

OverNumberException라는 예외를 하나 만들었다. 예외의 최고 조상인 Exception을 상속받고,

여러 메소드들을 통해 예외타입을 다룰 수 있다. 이 예제에서는 super()생성자를 통해 --1을 구현했다.

예외 연결하기

예외들간에도 관계들이 있을 것이다.

OverNumberExcpetion(), LowerNumberExcpetion()이라는 예외는 WrongNumberExcepction()이라고 묶을 수 있을 것이다.

어떠한 예외의 원인인 예외들의 관계를 통해 보다 정확화게 예외를 파악할 수 있다.

package playground;
class OverNumberException extends Exception{
    OverNumberException(String errMessage) {
        super(errMessage);
    }
}
class UnderNumberException extends Exception{
    UnderNumberException(String errMessage) {
        super(errMessage);
    }
}
class WrongNumberException extends Exception{
    WrongNumberException(String errMessage) {
        super(errMessage);
    }
}
public class playGround {
    public static void play(){
        try{
            playForException();
        }catch (WrongNumberException e){
            e.printStackTrace();
        }
    }
    static void playForException() throws WrongNumberException {
        int a = 1000;
        try{
            checkNum(a);
            System.out.println(100 / a);
        }catch (OverNumberException | ArithmeticException | UnderNumberException e){
            WrongNumberException we = new WrongNumberException("Wrong Number Value");
            we.initCause(e);  // --1
            throw we;
        }
    }
    static void checkNum(int a) throws OverNumberException, ArithmeticException, UnderNumberException {
        if (a > 100){
            throw new OverNumberException("Over Value");
        }
        if (a == 0){
            throw new ArithmeticException("Zero Value");
        }
        if (a < 0){
            throw new UnderNumberException("Under Value");
        }
        System.out.println("Continue");
    }
}
// 실행 결과.
// a > 100이므로
// OverNumberException
// -> WrongNumberException의 원인이 됨. // .initCause(e)
// e.printStackTrace();

/*
playground.WrongNumberException: Wrong Number Value
	at playground.playGround.playForException(playGround.java:31)
	at playground.playGround.play(playGround.java:20)
	at playground.main.main(main.java:6)
Caused by: playground.OverNumberException: Over Value
	at playground.playGround.checkNum(playGround.java:38)
	at playground.playGround.playForException(playGround.java:28)
	... 2 more
*/

예외들을 연결시켜, WrongNumberValueOverValue에 의해 일어났다는 것을 알 수 있다.

profile
앞으로 넘어지기

0개의 댓글