예외

muz·2021년 4월 20일
0

Java

목록 보기
17/21
post-thumbnail

🧀 예외(Exception)

프로그램을 만든 프로그래머가 상정한 정상적인 처리에서 벗어나는 경우, 이를 처리하기 위한 방법을 예외(Exception)이라 한다.

오류가 발생할 때

두 수를 나누는 메소드를 만들어보자. 두 수 10과 0을 매개변수로 입력받고 메인에서 메소드를 호출하면 에러가 발생한다.

public void divide(){
        System.out.print("계산결과는 ");
        System.out.print(this.left/this.right); // 에러
        System.out.print(" 입니다."); // 에러
    }

이는 10/0을 했기 때문에 발생한 에러이다. 이 메소드의 바디를 다음과 같이 변경해보자.

 public void divide(){
        try {
            System.out.print("계산결과는 ");
            System.out.print(this.left/this.right);
            System.out.print(" 입니다.");
        } catch(Exception e){
            System.out.println("오류가 발생했습니다 : "+e.getMessage());
        }

위의 코드처럼 메소드 divide의 본문을 수정한 뒤, 메인에서 매개변수로 (10,0), (10,5)를 차례로 입력해보자. (10,0)에서는 계산결과는 오류가 발생했습니다 : / by zero 가 출력되고, 계산결과는 2 입니다. 가 출력된다. 이번 코드를 실행하면, 위의 예제와 달리 오류가 발생하지 않는다. 마치 정상적인 에플리케이션인 것처럼 동작하고 있다. 이는 try-catch문을 사용했기 때문이다.

🧀 예외의 문법

try-catch문

try-catch문은 예외에서 핵심적인 역할을 담당하는 문법적인 요소이다.

try {
	예외의 발생이 예상되는 로직 
} catch (예외 클래스 인스턴스) {
	예외가 발생했을 때 실행되는 로직 
}

try

try 안에는 예외 상황이 발생할 것으로 로직을 위치시킨다.

catch

catch안에는 예외가 발생했을 때 뒷수습을 하기 위한 로직이 위치한다.

예외 클래스와 인스턴스

} catch(Exception e){
    System.out.println("오류가 발생했습니다 : "+e.getMessage());
}

위의 코드에서 e는 변수이다. 이 변수 앞의 Exception은 e의 데이터 타입을 의미한다. Exception은 자바에서 기본적으로 제공하는 클래스로 java.lang에 속해있다.

e.getMessage()는 자바가 전달한 인스턴스의 메소드 중 메세지를 호출하는 코드인데, getMessage는 오류의 원인을 사람이 이해하기 쉬운 형태로 리턴하도록 약속되어 있다.

Exception e 사용하기

  • e.getMessage()
    오류에 대한 기본적인 내용을 간단히 출력해준다.
  • e.toString()
    e.getMessage()보다 더 자세한 예외 정보를 제공한다. 위의 코드는 수학적인 계산 과정에서 발생하는 예외 상황이므로 실행 시 java.lang.ArithmeticException 예외 상황이 발생할 것이다.
  • e.printStackTrace()
    printStackTrace는 리턴 값이 없다. 이 메소드를 호출하면 메소드가 내부적으로 예외 결과를 화면에 출력하는데, 위의 e.getMessage()나 e.toString()보다 더 자세한 예외 정보를 제공한다.

다양한 Exceptions

같은 로직임에도 상황에 따라서 다른 예외가 발생할 수 있다.

  • ArrayIndexOutOfBoundsException
    존재하지 않는 값을 가져오려고 시도할 경우 발생되는 예외
  • ArithmeticException
    수학적인 계산 과정에서 발생하는 예외로, 0으로 나누는 등의 경우에 발생한다.

다중 catch문

조건문 else if처럼 여러 개의 catch를 하나의 try 구문에서 사용할 수 있다.

class Text{
    private int[] arr_ex = new int[3];
    Text() {
        arr_ex[0] = 0;
        arr_ex[1] = 10;
        arr_ex[2] = 20;
    }
    public void z(int num1, int num2) {
        try {
            System.out.println(arr_ex[num1]/arr_ex[num2]);
        } catch(ArrayIndexOutOfBoundsException e) {
            System.out.println("ArrayIndexOutOfBoundsException");
        } catch(ArithmeticException e) {
            System.out.println("ArithmeticException");
        } catch(Exception e) {
            System.out.println("Exception");
        }
    }
}

public class arr {

    public static void main(String[] args) {
        Text text = new Text();
        text.z(10,0); // ArrayIndexOutOfBoundsException
        text.z(1,0);  // ArithmeticException
        text.z(2,1); // Exception
    }
    
}

catch문을 사용할 때에도 주의해야 할 점이 있다. 위의 코드에서 메소드z의 코드를 수정해보자.

public void z(int num1, int num2) {
        try {
            System.out.println(arr_ex[num1]/arr_ex[num2]);
        } catch(Exception e) {
            System.out.println("Exception");
        } catch(ArithmeticException e) {
            System.out.println("ArithmeticException");
        } catch(ArrayIndexOutOfBoundsException e) {
            System.out.println("ArrayIndexOutOfBoundsException");
        }
}

메소드z를 위의 코드처럼 바꾼 후, 다시 실행해보자.
Exception은 ArrayIndexOutOfBoundsException, ArithemeticException 보다 포괄적인 예외를 의미하기에 Exception 이후에 등장하는 catch문은 실행될 수 없다.

try-catch-finally

try-catch문에 finally까지 사용할 수 있다.

try{
	예외의 발생이 예상되는 로직
} catch (예외클래스 인스턴스) {
	예외가 발생했을 때 실행되는 로직
} finally {
	예외여부와 관계없이 실행되는 로직
}

finally는 try 구문에서 예외가 발생하는 것과 상관없이, 언제나 실행되는 로직이다. 이번에는 finally를 이용하여 메소드z의 내용을 수정해보자.

 public void z(int first, int second){
        try {
            System.out.println(arr[first] / arr[second]);
        } catch(ArrayIndexOutOfBoundsException e){
            System.out.println("ArrayIndexOutOfBoundsException");
        } catch(ArithmeticException e){
            System.out.println("ArithmeticException");
        } catch(Exception e){
            System.out.println("Exception");
        } finally {
            System.out.println("finally");
        }
    }

예외와 상관없이 try내의 구문이 실행되면 finally가 실행된다.

finally는 특정 작업을 예외와 상관없이 반드시 끝내줘야 하는 경우에 사용한다.

🧀 Throws

예외의 강제

 public static void main(String[] args) {
        BufferedReader bReader = new BufferedReader(new FileReader("out.txt"));
        String input = bReader.readLine();
        System.out.println(input); 
    }

코드를 작성하다보면 로직에 대한 예외 처리가 필요함을 알려주는 경우가 있다. 예를 들어 다음과 같이 결과가 나온 경우가 예외 처리가 필요함을 알려주는 것이다.

Exception in thread "main" java.lang.Error: Unresolved compilation problems: 
    Unhandled exception type FileNotFoundException
    Unhandled exception type IOException

Unhandled exception type FileNotFoundException 라는 것은 FileReader와 관련된 로직에 예외처리가 필요하다는 뜻이다.

Constructor

자바 공식문서를 이용하여 FileReader에 대해 알아보자.

Parameters아래에 Throws라는 것이 있다.

Throws에는 어떠한 경우에 오류가 발생하는지에 대해 나와있다. 사진 속 내용은 생성자 FileReader의 인자 fileName의 값에 해당하는 파일이 디렉토리이거나 어떠한 이유로 사용할 수 없으면 FileNotFoundException을 발생시킨다는 것이다.

FileReader의 생성자가 동작할 때 파일을 열 수 없는 경우가 생길 수 있고, 이 경우 생성자 FileReader에서는 이 문제를 처리할 수 없어 처리를 생성자의 사용자에게 위임하겠다라는 것이다. 이를 throw(던진다)라고 표현하다. 따라서 API의 사용자는 예외에 대한 처리를 반드시 해주어야 한다.

예외에 대한 처리를 하는 방법 : try-catch문 사용하기

 public static void main(String[] args) {
        BufferedReader bReader = null;
        String input = null;
        try {
            bReader = new BufferedReader(new FileReader("out.txt"));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        try{
            input = bReader.readLine();
        } catch (IOException e){
            e.printStackTrace();
        }       
        System.out.println(input); 
    }
  • 클래스BuffereadReader의 메소드readLine은 IOExecption을 발생시킬 수 있다.

throw vs throws

위에서 예외 처리 방법으로 'try-catch-finally'를 배웠다. 이 방법 외에도, throw를 사용하는 방법이 있다. throw는 예외 처리를 다음 사용자에게 넘기는 것이다.

  • try-catch문 이용하기
import java.io.*;

class B{
    void run() {
        BufferedReader bReader = null;
        String input = null;
        try {
            bReader = new BufferedReader(new FileReader("out.txt")); // FileReader의 생성자
        } catch(FileNotFoundException e) {
            e.printStackTrace();
        }

        try {
            input = bReader.readLine();
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println(input);
    }
}
class C{
    void run() {
        B b = new B();
        b.run();
    }
}

public class excode {
    public static void main(String[] args) {
        C c = new C();
        c.run();
    }
}
  • throws문 사용하기
import java.io.*;

class B{
    void run() throws IOException, FileNotFoundException{
        BufferedReader bReader = null;
        String input = null;
        bReader = new BufferedReader(new FileReader("out.txt")); // FileReader의 생성자
        input = bReader.readLine();
        System.out.println(input);
    }
}
class C{
    void run() throws IOException, FileNotFoundException{
        B b = new B();
        b.run();
    }
}

public class excode {
    public static void main(String[] args) {
         C c = new C();
         try {
            c.run();
        } catch (FileNotFoundException e) {
            System.out.println("out.txt 파일은 설정 파일 입니다. 이 파일이 프로젝트 루트 디렉토리에 존재해야 합니다.");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }   
}

try-catch문이 제거되고 throws IOException, FileNotFoundException이 추가되었다. 이는 B.run 내부에서 IOException, FileNotFoundException의 예외가 발생하면 이에 대한 처리를 B.run의 사용자에게 위임하는 것이다. B.run의 사용자는 C.run이다.

🧀 예외 만들기

class Calculator{
    int left, right;
    public void setOprands(int left, int right){        
        this.left = left;
        this.right = right;
    }
    public void divide(){
        if(this.right == 0){
            throw new ArithmeticException("0으로 나누는 것은 허용되지 않습니다.");
        }
        try {
            System.out.print("계산결과는 ");
            System.out.print(this.left/this.right);
            System.out.print(" 입니다.");
        } catch(Exception e){
            System.out.println("\n\ne.getMessage()\n"+e.getMessage());
            System.out.println("\n\ne.toString()\n"+e.toString());
            System.out.println("\n\ne.printStackTrace()");
            e.printStackTrace();
        }
    }
} 
public class CalculatorDemo {
    public static void main(String[] args) {
        Calculator c1 = new Calculator();
        c1.setOprands(10, 0);
        c1.divide();
    }
}

throw는 예외를 발생시킨다. throw뒤에는 예외 정보를 가지고 있는 예외 클래스가 위치하며, 자바 가상 머신이 이 클래스를 기준으로 어떤 catch 구문을 실행할 것인지를 결정한다. 실행되는 catch 구문에서는 예외 클래스를 통해 예외 상황의 원인에 대한 다양한 정보를 얻을 수 있다.

Java에서 기본적으로 제공하는 예외들

자바 API문서를 통해 클래스Exception의 하위 클래스들에 대해서 살펴보자.

IOException

IOException은 try-catch문이나 throw를 해야만 한다. 같은 Exception일지라도 IOException처럼 특이한 것들이 있다.

Throwable

클래스ArithmeticException은 Exception의 하위 클래스이다. 클래스 Exception은 더 많은 예외 상황을 포괄하지만, 클래스 ArithmeticException은 더 구체적인 상황을 특정한다. 상속 관계를 자세히 살펴보면 java.lang.Throwable클래스가 있다. 클래스Throwable의 API문서 내용을 확인해보자.

checked 예외 vs unchecked 예외

  • checked 예외 : RuntimeException을 제외한 Exception의 하위 클래스
    → 반드시 예외 처리를 해야 함
  • unchecked 예외 : RuntimeException의 하위 클래스
    → 예외 처리를 해도 되고, 안해도 됨

Reference
1. 생활코딩_예외1
2. 생활코딩_예외2
3. 생활코딩_예외3

profile
Life is what i make up it 💨

0개의 댓글