JAVA 기초 -(43) 예외 처리 방법

코린이서현이·2023년 9월 9일
0

Java

목록 보기
45/46

🙂들어가면서🙂

어제 자바에서 규정하는 오류와 예외에 대해서 알아보았다.
오늘은 실제로 예외를 처리하는 방법에 대해서 알아보자.

📕 예외 처리 방법

예외처리에는 꼭 예외처리를 해야 프로그램이 실행되는 Checked 예외UnChecked 예외가 있다.

예외 처리 방법에는 잘 알려진 try-catch문과 throw문(예외 던지기), chained Exception문(예외 연결)이 있다.

📖 try-catch문

📒 try-catch문

try문에서 특정 예외 클래스가 발생하면 그 클래스에 대한 catch문이 실행되고 프로그램이 비정상 종료되지 않는다.
예외가 발생한다고 무조건 catch문이 실행되는 것은 아니고, 발생한 예외에 대한 catch문이 실행된다.
✍️ 실제코드

  • try문이 없으면 비정상 종료된다.
public class ExcetionTest {
  public static void main(String[] args) {
    int[] arr = new int[5];

    try {
      for(int i = 0; i <= 5; i++) {
        arr[i] = i + 1;				// → ArrayIndexO... 예외발생
        System.out.println(arr[i]);   
      }
    } catch (ArrayIndexOutOfBoundsException e) {//try문에 대한 예외
      System.out.println(e);
      System.out.println("예외처리를 마쳤습니다.");
    }
    System.out.println("프로그램 정상종료");
  }
}

👉 실행화면

1
2
3
4
5
java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 5
예외처리를 마쳤습니다.
프로그램 정상종료

📒 Excetion에 대한 catch문

모든 예외에 대한 catch문을 작성하는건 불가능하다. 따라서 예외클래스의 상위클래스인 Excetion에 대한 catch문을 작성하면 나머지의 모든 예외 클래스에 대해 작성할 수 있다.
다만, Excetion에 대한 문장은 맨 뒤에 와야한다. (상위클래스로 예외클래스를 한번에 처리하기 때문이다.)

📒 try - catch - finally문

finally문은 예외발생여부에 상관없이 무조건 실행된다.
try문이나 catch문에 return문이 있더라도 실행된다.

✍️실행코드

public class ExcetionTest {
  public static void main(String[] args) {

    try {
      System.out.println("정상처리");
      return;
    } catch (Exception e) {
      System.out.println("예외처리");
      return;
    } finally {
      System.out.println("마지막문장");
    }
    //System.out.println("프로그램 정상종료");
  }

}

👉실행 화면

정상처리
마지막문장

📒 멀티 catch문

여러 예외에 대한 catch문을 | 기호를 사용해서 한번에 작성할 수 있다.

try {
    // ...
} catch (NullPointException | ArrayIndexOutOfBoundsExcetion e) {
    // ...
}

📒 try-with-resources

try-with-resources문에서 말하는 "resources"는 파일, 데이터베이스, 네트워크와 같은 외부의 데이터를 말한다.
resources는 사용 후에 닫아야하는 특징을 가진다.
👉 리소스에 대한 try-catch문을 작성해야하는데, 예외 발생여부와 상관없이 리소스 해제문 작성이 필수이고, 이는 또 try-catch문의 작성이 요구되어 코드의 가독성이 안좋아진다는 단점이 있다.

자바에서는 이를 위해 파일을 열거나 자원을 할당하는 리소스 선언부분을 try문의 괄호안에 선언하면 try 블록이 끝날 때 자동으로 리소스를 해제해준다.

✍️ 예제코드

import java.io.FileWriter;
import java.io.IOException;

public class Main {
    public static void main(String[] args) {
        try (FileWriter file = new FileWriter("data.txt")) { // 파일을 열고 모두 사용되면 자동으로 닫아준다
            file.write("Hello World");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

➕ try에 괄호안에는 세미콜론으로 구분해 여러 리소스를 선언할 수 있다.

🔍 AutoCloseable 인터페이스

리소스를 자동 해제해주는 try-with-resources 문으로 사용되기 위해서는 해당 객체가 AutoCloseable 인터페이스를 구현한 클래스여야한다.

📗 예외 메서드

예외 클래스가 가지고 있는 메서드를 이용해 예외 메세지를 출력할 수 있다.

printStackTrace()

  • 예외발생 당시의 호출스택(Call Stack)에 있었던 메서드의 정보와 예외 메시지를 화면에 출력
  • 프로그램의 내부 요소를 자세하게 추적하여 오류 메세지를 내보이기에 이를 외부에 노출시키면 보안적인 문제가 될 수 있으니 관계자만 확인 할 수 있도록 만들어 주는 것이 좋다.

getMessage()

  • 발생한 예외클래스의 인스턴스에 저장된 메시지를 얻을 수 있다.

📖 예외 던지기

📒 예외 발생시키기

예외를 발생시켜 로그에 기록하고 싶을 때 throw키워드를 사용하여 예외를 발생시킬 수 있다.
throw 예약어와 new생성자로 로 원하는 예외 클래스를 초기화하여 발생시킬 수 있다.
이때 생성자의 매개변수는 getMessage() 메서드에서 출력할 메세지가 된다.

try{
      Scanner scanner = new Scanner(System.in);
      System.out.println("자연수 입력 : ");
      int input = scanner.nextInt();
      if(input <= 0){
        throw new ArithmeticException("사용자가 자연수가 아닌 수를 입력함.");
      }
    } catch(ArithmeticException e){
      System.out.println(e.getMessage()); // 사용자가 자연수가 아닌 수를 입력함을 출력
    }
  }

📒 예외 떠넘기기 throws

throws는 해당 메서드 내애서 직접 예외처리를 하지 않고 예외를 호출한 곳으로 떠넘긴다.
여러 코드에서 호출돼 사용될 때, 호출 상황에 맞게 로그를 남기거나 호출 부분에서 예외처리를 하도록 한다.

✍️ 실행코드

  • loadClass메서드는 예외처리가 필수인 코드를 가지고 있다.
    FileInputStream fis = new FileInputStream(fileName);
    → FileNotFoundException에 대한 예외처리 필수
    Class c = Class.forName(className);
    → ClassNotFoundException에 대한 예외처리 필수

  • 그런데 예외가 발생할 수 있는 메서드 내에서 예외처리를 하는 것이 아니라, 이 메서드를 호출하는 곳에서 예외처리를 하도록 throws예약어를 사용하는 것이다.

package Do_It_Java_14;

import java.io.FileInputStream;
import java.io.FileNotFoundException;

public class ThrowsExcetion {
  public Class loadClass(String fileName, String className) throws FileNotFoundException, ClassNotFoundException {
    FileInputStream fis = new FileInputStream(fileName);
    Class c = Class.forName(className);
    return c;
  }

아래 호출 부분에서 각각의 예외에 대한 catch블럭을 작성한 것을 확인 할 수 있다.

  public static void main(String[] args) {
    ThrowsExcetion test = new ThrowsExcetion();
    try {
      test.loadClass("a.text", "java.lang.String");
    } catch (FileNotFoundException e) {
      System.out.println(e.getMessage());
    } catch (ClassNotFoundException e) {
      System.out.println(e.getMessage());
    }

  }
}

👉 실행화면

a.text (지정된 파일을 찾을 수 없습니다)

➕ main함수에서 떠넘기면 어떻게 될까?

만약 main메서드에서까지 떠넘기면 JVM으로 떠넘겨져 프로그램이 종료된다.

✍️ 예시코드

import java.io.FileInputStream;
import java.io.FileNotFoundException;

public class ThrowsExcetion {
  public Class loadClass(String fileName, String className) throws FileNotFoundException, ClassNotFoundException {
    FileInputStream fis = new FileInputStream(fileName);
    Class c = Class.forName(className);
    return c;
  }

  public static void main(String[] args) throws FileNotFoundException, ClassNotFoundException {
    ThrowsExcetion test = new ThrowsExcetion();
    
    test.loadClass("a.text", "java.lang.String");

  }
}

👉 실행화면
프로그램이 강제 종료됌

Exception in thread "main" java.io.FileNotFoundException: a.text (지정된 파일을 찾을 수 없습니다)
	at java.base/java.io.FileInputStream.open0(Native Method)
	at java.base/java.io.FileInputStream.open(FileInputStream.java:216)
	at java.base/java.io.FileInputStream.<init>(FileInputStream.java:157)
	at java.base/java.io.FileInputStream.<init>(FileInputStream.java:111)
	at Do_It_Java_14.ThrowsExcetion.loadClass(ThrowsExcetion.java:8)
	at Do_It_Java_14.ThrowsExcetion.main(ThrowsExcetion.java:16)

🤔 트랜잭션에 대해서 생각해보기

try-catch문을 사용하느냐, throws를 하느냐에 따라서 자바 코드의 작업 단위가 달라질 수 있기 때문에 자신의 프로젝트에 따라 적절한 예외 처리 로직을 짜야한다.
더 알아보기

📖 예외 연결하기

예외 클래스도 상속이 있기 때문에 부모예외로 감싸 예외의 다향성처럼 다룰 수 있다.
예외 A가 발생했을 때 예외 B로 감싸서 throw를 할 수 있다.
이때 예외 A를 "원인 예외"라고 한다.

  1. 예외를 연결하는 방법
    예외A가 발생한 상황에서! (catch문에서 작성)
    원하는 예외B의 객체를 만든다.
    예외B의 객체에 예외A의 객체를 연결한다.
    예외B initCause (예외A) : 지정한 예외를 원인 예외로 등록
    예외B를 발생시킨다.
  2. 원인 예외 반환
    예외B getCause() : 원인예외를 반환

✍️ 예시코드
1. startInstall() 메서드에서 SpaceException 이라는 예외가 발생
2. catch 문에서 InstallExceptoin 예외 클래스를 새로 생성
3. 그리고 InstallException 객체의 메서드 initCause()를 이용해 SpaceException 타입의 객체를 넣어 실행
4. 그러면 SpaceException 예외는 InstallException 예외에 포함되게 된다. (원인 예외)
5. InstallException 예외 객체발생시키고 밖으로 throws 한다.
6. 메인메서드에서 InstallException 예외를 catch하고 getCause() 메서드를 통해 원인 예외 로그를 출력한다

class InstallException extends Exception { ... }
class SpaceException extends Exception { ... }
class MemoryException extends Exception { ... }

public class Main {
    public static void main(String[] args) {
        try {
            install();
        } catch (InstallException e) {
            System.out.println("원인 예외 : " + e.getCause()); // 원인 예외 출력
            e.printStackTrace();
        }
    }

    public static void install() throws InstallException {
        try {
            throw new SpaceException("설치할 공간이 부족합니다."); // SpaceException 발생

        } catch (SpaceException e) {
            InstallException ie = new InstallException("설치중 예외발생"); // 예외 생성
            ie.initCause(e); // InstallException의 원인 예외를 SpaceException으로 지정
            throw ie; // InstallException을 발생시켜 상위 메서드로 throws 된다.
        } catch (MemoryException e) {
            // ...
        }
    }
}

🔍 예외를 연결하는 것의 장점

  1. 여러가지 예외를 하나의 큰 분류의 예외로 묶어서 다루기 위해서이다.
  2. 단계별로 예외의 정보를 주는 것이 좋다. (추척에 용이하다.)
  3. Checked 예외를 Unchecked 예외로 변환
throw new RuntimeException(new IOException("설치할 공간이 부족합니다."));
// Checked 예외인 IOException을 Unchecked 예외인 
RuntimeException으로 감싸 Unchecked 예외로 변신 시킨다

📖 사용자 예외 만들어보기

자바에서 제공하는 예외 말고도 원하는 예외가 따로 있을 수 있다.
그럴 때 사용자 예외를 만들 수 있다.
사용자 정의 예외 클래스를 구현할 때는 기존 JDK에서 제공하는 예외클래스중 가장 유사한 클래스를 상속받는 것이 좋다. (유사한 예외 클래스를 모르는 상황에서는 가장 상위클래스인 EXcetion클래스를 상속받는 것이 좋다.)

사용자 예외 클래스는 사용자가 원하는 발생시점에 throw를 통해서 발생시켜야한다.

✍️실행코드

package Do_It_Java_14;

import java.util.Scanner;

class IDExcetion extends Exception {
  private String msg;

  public IDExcetion(String msg) {
    super(msg);
    this.msg= msg;
  }
  public void printIDExcetionMessage() {
    System.out.println(this.msg);
  }
}

class IDformat {
  private String ID;
  public void setID(String ID) throws IDExcetion {
    if(ID == null) {
      throw new IDExcetion("아이디가 null");
    } else if (ID.length() < 8 || ID.length() > 20 ) {
      throw new IDExcetion("아이디는 8자 이상, 20자 이하입니다.");
    } else {
      this.ID = ID;
    }
  }
}

public class CustomExcetionTest {
  public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);
    IDformat iDformat = new IDformat();
    try {
      String ID = sc.nextLine();
      iDformat.setID(ID);
    } catch (IDExcetion e) {
      e.printIDExcetionMessage();
    }
  }
}

😂마무리하면서😂

휴~ 재미읎다~
profile
24년도까지 프로젝트 두개를 마치고 25년에는 개발 팀장을 할 수 있는 실력이 되자!

0개의 댓글