Java Exception

irenett·2024년 10월 8일

🥎 Java의 Exception에 대해 설명해 주세요.

Exception은 프로그램 실행 흐름을 방해하는 사건을 말합니다. 예외가 발생하면 프로그램이 비정상적으로 종료될 수 있으며, Java의 예외 처리 메커니즘을 통해 이러한 오류를 관리하여 프로그램이 복구하거나 정상적으로 종료되도록 합니다.

Class Throwable
Java에서 Exception은 Throwable 클래스를 상속받는 객체로 표현됩니다.
Throwable 클래스는 두 가지 주요 하위 클래스인 ErrorException을 가집니다.

Exception 전체 구조

                 Throwable
                 /         \
             Error          Exception
                           /        \
              (UncheckedException)   CheckedExceptions
                RuntimeException
  • Throwable
    • Throwable 클래스는 모든 예외와 오류의 최상위 클래스입니다.
    • ErrorException 클래스를 포함합니다.
  • Error
    • 시스템 레벨에서 발생하는 심각한 문제를 나타내며, 보통 개발자가 직접 처리하지 않는 예외입니다.
    • OutOfMemoryError, StackOverflowError 등이 있으며, 이러한 오류는 주로 JVM에서 발생하며 예외처리를 통해 애플리케이션 레벨에서 이를 정상적인 상태로 되돌리거나 문제를 해결하는 것이 거의 불가능합니다.
  • Exception
    - 프로그램에서 처리할 수 있는 조건으로, 프로그램 실행 중 발생할 수 있는 예외적인 상황을 나타냅니다.
    • Exception 클래스는 크게 Checked ExceptionUnchecked Exception으로 나뉩니다.

🥎 CheckedException, UncheckedException의 차이에 대해 설명해 주세요.

예외 종류

  1. Checked Exception

    • 컴파일러가 컴파일 시점에 검사하는 예외입니다.
    • IOException, SQLException, ClassNotFoundException 등이 여기에 속하며, 파일 입출력, 네트워크 작업, 데이터베이스 연결, 네트워크 통신 등에서 발생할 수 있는 예외입니다.
    • try-catch 블록이나, throws 키워드를 사용하여 예외를 처리하도록 명시해야 합니다.
    //file 찾을때 없으면 예외 던지기 때문에 예외 처리
    try {
        FileReader reader = new FileReader("file.txt");
    } catch (IOException e) {
        e.printStackTrace();
    }
  2. Unchecked Exception (= RuntimeException 클래스와 그 하위 클래스)

    • 런타임 시점에서 발생하며, 컴파일 시점에서 컴파일러가 처리 여부를 검사하지 않습니다.
    • 주로 개발자의 실수 혹은 논리 오류로 발생하며, 예외 처리가 필수는 아니지만, 예외를 처리하면 프로그램의 안정성을 높일 수 있습니다.
    • RuntimeException 클래스와 그 하위 클래스들이 이에 속합니다. 예를 들면 NullPointerException, ArrayIndexOutOfBoundsException, ,IllegalArgumentException 등이 있습니다.
    • 예외 처리가 필수는 아니지만, 프로그램의 안정성을 위해 종종 처리하는 것이 좋습니다.
    public void divide(int a, int b) {
    	if (b == 0) {
       	 	 throw new ArithmeticException("0으로 나눌 수 없습니다.");
    	 }
    
         System.out.println(a / b);
     }

요약

특성CheckedExceptionUncheckedException
발생 시점컴파일 시점런타임 시점
강제 처리 여부Y, 반드시 처리해야 함N, 처리하지 않아도 됨
상속 구조Exception 클래스에서 파생 (단, RuntimeException 제외)RuntimeException 클래스와 그 하위 클래스
주로 발생하는 상황외부 자원 접근, 파일 입출력, 데이터베이스 등프로그래머 실수, 논리 오류 등
예시IOException, SQLExceptionNullPointerException, ArithmeticException
  • CheckedException은 컴파일 시점에 반드시 처리해야 하는 예외로, 컴파일러가 예외 처리 여부를 검사합니다.
  • UncheckedException은 런타임 시점에 발생하며, 컴파일러가 예외 처리 여부를 강제하지 않습니다.

예외 처리 구문

Java는 예외를 처리하기 위해 try, catch, finally, throw, throws와 같은 키워드를 제공합니다.

  • try : 예외가 발생할 수 있는 코드를 감쌉니다.
  • catch : 예외가 발생했을 때 이를 처리하는 코드를 작성합니다.
  • finally : 예외 발생 여부와 상관없이 항상 실행되는 블록으로, 주로 파일이나 데이터베이스 연결 같은 자원을 해제할 때 사용됩니다.
  • throw : 명시적으로 예외를 발생시킬 때 사용합니다.
  • throws : 메서드 선언부에서 해당 메서드가 특정 예외를 던질 수 있음을 명시합니다. 이를 통해 호출하는 쪽에서 예외를 처리할 수 있도록 합니다.

🥎 예외처리를 하는 세 방법에 대해 설명해 주세요.

try-catch-finally 예시 = 예외를 직접 처리하기
예외가 발생한 메서드 내에서 try-catch 블록을 사용하여 예외를 처리하는 방식입니다. 예외가 발생할 가능성이 있는 코드를 try 블록으로 감싸고, 발생한 예외를 catch 블록에서 처리합니다. 이 방법은 예외가 발생한 시점에서 문제를 바로 해결할 수 있게 해 줍니다.

    public void readFile(String fileName) throws IOException {
        try {
            FileReader reader = new FileReader(fileName);
            // 파일 읽기 작업 수행하..겠지?
        } catch (IOException e) {
            // 파일 열려고 했다가 실패했다는 것을 상위 메서드에게 알리기(전파)
            throw e;
        } finally {
            // 자원 정리
            if (reader != null) {
            	try {
                	reader.close();
            	} catch (IOException closeException) {
                	//원래 예외와 부딪힐 수 있기 때문에 로그만 남기기
                	System.err.println("파일을 닫는 중 오류가 발생했습니다: " + closeException.getMessage());
            	}
        	}
        }
    }

throw 예시 = 예외를 명시적으로 던지기
프로그램에서 특정 상황을 만나면 예외를 명시적으로 던지는 방법입니다. 주로 메서드 내부에서 잘못된 조건이 발생했을 때, throw 키워드를 사용하여 예외를 발생시킵니다.
예외가 RuntimeException 계열이든 Checked Exception이든 상관없이, throw를 통해 던질 수 있습니다.
하지만 Checked Exception을 던질 경우, 해당 예외가 발생할 수 있음을 메서드 선언부에 throws 키워드를 통해 명시해야 합니다. 그렇지 않으면 컴파일 오류가 발생합니다.

	public void validateAge(int age) {
	    if (age < 0) {
    	    throw new IllegalArgumentException("나이는 음수일 수 없습니다.");
    	}
	}

throws 예시 = 예외를 호출한 메서드로 던지기
메서드에서 발생한 예외를 자신이 처리하지 않고 상위 메서드로 던지는 방식입니다. 메서드 선언부에 throws 키워드를 사용하여 호출한 메서드가 예외를 처리하도록 합니다. 주로 Checked Exception을 처리할 때 사용됩니다.

	public void connectToDatabase() throws SQLException {
    	try {
        	// 데이터베이스에 연결 시도
	        connection = DriverManager.getConnection(url, username, password);
    	    System.out.println("데이터베이스에 성공적으로 연결되었습니다.");
        
        	// 데이터베이스 작업 수행 (여기에서는 생략)
    	} catch (SQLException e) {
        	System.err.println("데이터베이스 연결 중 오류가 발생했습니다: " + e.getMessage());
        	throw e; // 예외를 다시 던짐
    	}
	}

정리

Catch: 예외를 직접 처리하는 방식
Throws: 예외를 상위 메서드로 던지는 방식
Throw: 조건에 따라 명시적으로 예외를 발생시키는 방식

🥎 예외처리가 성능에 큰 영향을 미치나요? 만약 그렇다면, 어떻게 하면 부하를 줄일 수 있을까요?

Java에서 예외 처리는 성능에 영향을 미칠 수 있으며 일반적인 프로그램보단 예외가 자주 발생하거나 성능이 중요한 애플리케이션(실시간 혹은 고성능)에서는 주의해야합니다.

예외 처리가 성능에 미치는 영향

  1. 예외 발생 시 성능 저하:
    • Java에서 예외가 발생할 때는 호출 스택을 추적하고 예외 객체를 생성합니다. 이 과정은 일반적인 메서드 호출이나 조건문 처리보다 많은 비용이 듭니다.
    • 특히, 예외가 자주 발생하거나 반복문 안에서 예외를 다루는 경우 성능 저하가 더 심각해질 수 있습니다.
  2. 정상적인 흐름에서 예외를 사용하면 비효율적:
    • 예외 처리는 예외적인 상황을 처리하기 위한 구조입니다. 만약 예외를 정상적인 흐름에서 사용하는 경우 프로그램의 성능이 크게 저하될 수 있습니다.
    • 예외 객체 생성 비용 : 예외가 발생할 때, Java는 예외 객체를 생성해야 합니다. 이 객체는 예외 발생 시의 스택 트레이스(stack trace) 정보를 포함하는데, 이 과정에서 JVM은 스택 프레임을 모두 살펴보고, 현재 상태를 캡처해야 하기 때문에 CPU 연산과 메모리 할당이 추가로 필요합니다. 따라서 일반적인 조건문보다 더 많은 자원을 소모합니다.
    • 예외 처리를 위한 JVM의 추가 작업: 예외가 발생하면 JVM은 현재 실행 중인 코드를 중단하고, 예외를 처리하기 위해 catch 블록 또는 호출 스택의 상위 메서드를 탐색합니다. 이 과정에서 호출 스택을 탐색하고, 예외 핸들러를 찾기 위해 더 많은 계산이 필요합니다.

예외 처리로 인한 부하를 줄이는 방법

  1. 예외를 예외적인 상황에서만 사용하기:

    • 예외적인 상황에서만 사용해야 하며, 일반적인 제어 흐름에서는 사용하지 않아야 합니다. 예를 들어, 입력 값 null 검증을 통해 예외 발생을 방지하거나, 배열의 인덱스가 범위를 벗어났는지 검사할 때는 조건문을 사용해 검사하는 것이 좋습니다.
      // ppp 비효율적인 코드 (예외를 정상적인 흐름에 사용)
      try {
          int value = array[index];
      } catch (ArrayIndexOutOfBoundsException e) {
          // 예외 처리 코드
      }
      
      // bbb 효율적인 코드 (조건문 사용)
      if (index >= 0 && index < array.length) {
          int value = array[index];
      } else {
          // 오류 처리 코드
      }
      
  2. 예외를 캐치한 후 불필요한 작업을 줄이기:

    • 예외가 발생했을 때, 로깅을 하는 것은 중요하지만, 예외의 스택 트레이스를 항상 출력하면 성능에 영향을 줄 수 있습니다. 필요한 경우에만 예외 정보를 상세하게 로깅하고, 성능이 중요한 경우에는 로그 수준을 적절하게 조정하거나 간략한 메시지만 남기는 것이 좋습니다.
  3. 예외가 자주 발생하는 코드의 구조를 최적화하기:
    - 예외가 자주 발생하는 구간이 있다면 해당 구간의 로직을 재설계하거나, 예외 발생을 최소화할 수 있는 다른 방법을 모색해야 합니다.

    필요한 경우에는 커스텀 예외를 사용해 명확성/유지보수성 향상:
    - RuntimeException이나 다른 표준 예외를 사용하는 대신, 상황에 맞는 커스텀 예외를 정의해 사용함으로써 불필요한 예외 발생을 줄일 수 있습니다. 특히, 예외 메시지나 로깅을 통해 문제의 원인을 쉽게 파악할 수 있도록 하는 것이 중요합니다.

0개의 댓글