[Java] Programming Exception Handling

immanuelk1m·2023년 5월 22일
0

Java

목록 보기
5/9
post-thumbnail

Exception이란

'프로그램 상의 이벤트'로 프로그램 실행 중에 정상적인 흐름을 방해하는 예외 처리 지시이다.

Java에서 Exception은 하나의 Object로 Error Data(State)와 Method(behavior)들을 가지고 있다.

method 내에서 Error가 발생하면, method는 exception object를 생성 해 Runtim System에 전달한다.

Method 호출 스택

왼쪽 그림(Call stack)은 프로그램이 실행되는 동안 method 호출의 순서를 추적하는데 사용되는 데이터구조로 일반적으로 method가 호출 될 때마다 해당 method가 스택의 맨 위에 추가되고 함수가 반환 될 때마다 Pop되며 프로그램 실행 순서를 나타낼 수 있다.

오른쪽 그림(Searching the call stack for the exeception handler)
예외가 발생했을 때 Exception을 찾는 과정을 나타낸다. Exception을 찾는 과정은 예외가 발생한 지점부터 호출 스택을 다시 되돌아가며 예외 처리를 찾을 때까지 각 호출 프레임에서 검색을 수행해 Exception을 발견하게 되면 예외처리를 하게 된다.

즉, Call stack은 프로그램의 실행 흐름을 추적하는 데 사용되는 데이터 구조이고, Searching the call stack for the exeception handler은 예외 처리기를 찾기 위해 호출 스택을 검색하는 과정이다.

Throwing & Handling

Exception은 크게 2가지 과정으로 나뉘는데 Throwing과 Handling이다.

Thorwing은 예외가 발생한 상황을 Exception Object로 정의하는 부분으로
Try blcok에서 Error가 발생할 것 같은 부분에 throw로 예외를 발생시킨다.

Handling은 예외가 발생했을 때, 조치를 취하는 부분으로 Catch block에서 따로 실행 시킬 코드를 작성한다.
Catch block의 Parameter는 Try Block에 선언한 thrown Exception Object이다.

기존에 If문을 사용해서 각 Case들을 생각해 예외를 처리했다면
Java에서는 Exception을 활용해 예외처리가 가능하다.

아래는 Exception (Try - Catch)을 다룬 예시이다.


Scanner keyboard = new Scanner (System.in);
System.out.println ("도넛 수를 입력:");
int donutCount = keyboard.nextInt ();
System.out.println ("우류 수를 입력:");
int milkCount = keyboard.nextInt ();

if (milkCount < 1) // 우유가 없다면!
{
  System.out.println ("우유 없음!");
  System.out.println ("가서 사와.");
} 
else // 우유가 있다면!
{
  double donutsPerGlass = donutCount / (double) milkCount;
  System.out.println (donutCount + " 도넛 개수");
  System.out.println (milkCount + " 우유 개수");
  System.out.println ("You have " + donutsPerGlass +
  " 우유 개수 당 도넛 수");
}
System.out.println ("End of program.")

아래는 위 코드를 Exception을 사용해 다시 작성한 코드이다.

try 
{
  System.out.println ("도넛 개수를 입력:");
  int donutCount = keyboard.nextInt ();
  
  System.out.println ("우유 개수를 입력:");
  int milkCount = keyboard.nextInt ();
  
  if (milkCount < 1) // 우유가 없으면..!!
  	throw new Exception ("Exception: 우유 없는디!!");
 	// Exception 발생!! 
    // 이후 아래 코드는 실행되지 않고
    // catch 부분으로 넘어감
    
  double donutsPerGlass = donutCount / (double) milkCount;
  System.out.println (donutCount + " donuts.");
  System.out.println (milkCount + " glasses of milk.");
  System.out.println ("You have " + donutsPerGlass +
  " donuts for each glass of milk.");
} 
catch (Exception e) // Exception Handling 부분
{
  System.out.println (e.getMessage ());
  System.out.println ("가서 우유 사와.");
}
System.out.println ("End of program.");

더 복잡해진 것 같지만 코드가 많아졌을 때, Exception을 사용하면 디버깅에 많은 장점이 있다.

Customized Exception

Java에는 미리 정의된 Exception도 있지만 개발자가 새로 Exception을 Customize 할 수 있다.

아래는 Exception Class를 상속 받아 새로운 DivideByZeroException를 만드는 예시이다.


public class DivideByZeroException extends Exception
{
  public DivideByZeroException ()
  {
  	super ("Dividing by Zero!");
  }
  public DivideByZeroException (String message)
  {
  	super (message);
  }
}

이런식으로 코드의 필요한 예외처리가 있다면 따로 Exception을 만들어 주면 된다.

Exception Method

Exception에는 getMessage() method가 정의 되어 있는데,
이 함수는 Exception이 발생했을 때 나타나는 Message를 string 형태로 return한다.

반대로 Exception의 Constructer의 parameter에 Message를 포함해 생성하게 되면, setMessage(string message)가 적용된다.

Customized Exception Rule

Exception Class를 만들 때에는 아래 규칙을 지켜서 만드는 것이 좋다.

  • parameter가 없는 Default 생성자와 String parameter가 있는 또 다른 생성자 최소 두 개를 만들 것.
  • 생성자에는 super() 를 사용해 부모 Class를 호출 할 것
  • getMessage method를 override 하지말 것

아래는 위 규칙을 따라 만든 Customized Exception 이다.

public class WrongFileNameException extends Exception 
{

    public WrongFileNameException() 
    {
        super();
    }

    public WrongFileNameException(String errorMessage) 
    {
        super(errorMessage);
    }
}

throw vs throws

throw는 예외를 발생시키는 데 사용되는 Keyword이고, 강제로 예외를 발생시킬 수 있다.

throws는 method 선언부에서 예외를 선언하는 데 사용되는 키워드로, method 내에서 해당 예외가 발생할 수 있다고 정하고, 예외 발생 시 해당 예외를 처리하도록 한다.

method 내에 throw를 따로 작성하지 않았다면 반드시 throws를 method 초반에 명시해줘서 Error 발생 시 Exception를 할 수 있도록 해야한다.

public String readFile(String filename) throws Exception, IOException, 
, NullPointerException // throws로 발생가능한 여러 예외 정의
{
  FileInputStream fis = new FileInputStream(filename);
  byte[] buffer = new byte[1024];
  int bytesRead;
  
  while ((bytesRead = fis.read(buffer)) != -1) 
  {
    // Do something with the bytes
  }
  
  fis.close();
  return new String(buffer);
}

Exception 처리 지연

위에서는 throw로 발생 시킨 Exception이 발견 되면 그 즉시 해당 Method 내에서 예외를 처리하게 하였는데, 예외 처리를 지연시키는 방법도 있다.

import java.io.*;

public class FileHandler {
    public void openFile(String filePath) throws IOException {
        try {
            // 파일 열기 작업
            FileInputStream fileInputStream = new FileInputStream(filePath);
            // 파일을 처리하는 로직 작성
            // ...
            fileInputStream.close();
        } catch (IOException e) {
            // IOException을 호출자에게 전달
            throw e;
        }
    }

    public static void main(String[] args) {
        FileHandler fileHandler = new FileHandler();
        String filePath = "path/to/file.txt";

        try {
            fileHandler.openFile(filePath);
        } catch (IOException e) {
            // 호출자인 main 메서드에서 IOException을 처리
            System.out.println("An error occurred while opening the file: " + e.getMessage());
        }
    }
}

throw를 던지고 exception을 method 내에서 catch 안 한다면?

메서드는 예외가 발생한 시점에서 즉시 종료된다.
예외가 발생하면 해당 예외를 처리하는 코드를 찾지 못하므로, 메서드의 실행은 중단되고 예외가 발생한 곳을 호출한 곳으로 전달된다.
이후 메서드 내에 있는 코드는 실행되지 않는다.

throws Overiding

만약 예외를 던지는 메서드가 슈퍼 클래스나 인터페이스의 메서드를 오버라이딩하는 경우, throws 절을 통해 선언된 예외를 조절할 수 있다.

더 많은 수의 예외를 선언하는 것은 허용되지 않고, Overiding된 Method는 super class의 메서드와 동일한 예외를 선언하거나, 더 적은 수의 예외를 선언할 수 있습니다.

class SuperClass {
    public void doSomething() throws IOException, SQLException {
        // 예외가 발생할 수 있는 작업
    }
}

class SubClass extends SuperClass {
    @Override
    public void doSomething() throws IOException {
        // IOException만 선언 가능
    }
}

Exception 계층 구조

위 그림처럼 Exception은 계층 구조를 가지고 있는데
크게 Error, Exception(Checked exception과 Unchecked exception)으로 나뉜다.

해당 내용은 아래 링크에 자세히 나와있다.
https://devlog-wjdrbs96.tistory.com/351

Error

Error 또한 Object인데, unchecked exception과 비슷하지만
exception은 개발자의 실수로 발생하는 상황을 다루지만, Error는 프로그램을 실행하다 개발자의 능력 밖의 상황인 비정상적인 상황을 다룬다. 따라서 catch나 throws를 사용할 수가 없다. Error의 예시로 OutOfMemoryError가 있다.

Multiple Throws and Catches

try 블록은 다른 유형의 예외를 여러 개 던질 수 있지만 각 catch 블록은 오직 하나의 exception만을 처리할 수 있다. catch 블록은 예외 유형을 지정하여 해당 예외 유형이 발생했을 때 예외를 처리한다. 그래서 catch 블록의 순서가 중요하다.

try {
    // 예외가 발생할 수 있는 작업
} catch (IOException e) {
    // IOException 처리
} catch (SQLException e) {
    // SQLException 처리
} catch (Exception e) {
    // 모든 예외를 처리하는 일반적인 catch 블록
}

Exception Handeling Anywhere

메서드에서 발생한 예외를 처리하기 위해 예외 처리를 사용합니다. 예외 처리는 예외가 발생한 곳과 상관없이 메서드 내 어디에서든지 발생할 수 있습니다. 즉, 예외는 예외를 발생시킨 메서드에서 직접 처리할 수도 있고, 호출한 곳으로 예외를 전파하여 처리할 수도 있습니다

public void divide(int x, int y) throws ArithmeticException 
{
  // 예외는 여기서 발생했지만
  if (y == 0) {
    throw new ArithmeticException("Division by zero!");
  }
  return x / y;
}

public void main(String[] args) {
  try 
  {
    int result = divide(10, 0);
    System.out.println(result);
  } 
  catch (ArithmeticException e) 
  {
    System.out.println(e.getMessage());
  } // 해결은 Main에서 함
}

throw 문은 어쩔 수 없는 경우 사용해야한다. 예외를 직접 발생시키는 것보다 특정 조건이 충족되지 않았을 때 예외를 던지는 것이 일반적입니다.

try - catch - finally

catch block 이후에 finally block을 작성하여 exception이 있거나 없거나
마지막으로 실행할 코드를 실행한다.

try 
{
	...
} 
catch(Exception e) 
{
	...
} 
finally 
{
	...
}
profile
개발 새발

0개의 댓글