응애개발자
article thumbnail
Published 2024. 5. 2. 23:15
[Java] Error와 Exception CS/Java
728x90

📌 Error(오류)와 Exception(예외)

오류

Error(오류)는 시스템이 종료되어야 할 수준의 상황과 같이 수습할 수 없는 심각한 문제를 의미합니다. 개발자가 미리 예측하여 방지할 수 없습니다.

 

예외

Exception(예외)는 개발자가 구현한 로직에서 발생한 실수나 사용자의 영향에 의해 발생하며 프로그램 실행 중 발생하는 이벤트로 프로그램 명령의 정상적인 흐름을 방해하는 것입니다. 오류와 달리 개발자가 미리 예측하여 방지할 수 있기에 상황에 맞는 예외처리(Exception Handle)를 해야 합니다.

 

자바의 Error와 Exception 모두 자바의 최상위 클래스인 Object를 상속받습니다. 그리고 그 사이에는 Throwable이라는 클래스와 상속 관계가 있습니다. 밑에서 Throwable 클래스에 대해 말씀드리겠습니다.

 

🔎 Throwable

공식 문서에 따르면 Throwable 클래스는 Object를 상속받아 클래스의 객체에 오류나 예외에 대한 메시지를 담습니다. 그리고 예외가 연결될 때(chained exception) 연결된 예외의 정보들을 기록하기도 합니다. 이 Throwable 객체가 가진 정보와 할 수 있는 행위는 getMessage()printStackTrace()라는 메서드로 구현되어 있는데, 당연히 이를 상속받은 Error와 Exception에서 두 메서드를 사용합니다.

 

그렇다면 밑에서 getMessage(), toString(), printStackTrace() 예제를 보여드리겠습니다.

package Example;

import java.util.ArrayList;
import java.util.List;

public class Test {
    public static void main(String[] args) {
        try {
            List<String> list = new ArrayList<>();

            //예외 발생
            System.out.println(list.get(0));

        } catch (Exception e) {
            System.out.println("#getMessage");
            System.out.println(e.getMessage());
            System.out.println();

            System.out.println("#toString");
            System.out.println(e.toString());
            System.out.println();

            System.out.println("#printStackTrace");
            e.printStackTrace();

        }
    }
}

 

결과

  • getMessage() : 발생한 예외 클래스의 인스턴스에 저장된 메세지를 얻을 수 있습니다. 아주 간단한 정보만 보여줍니다.
  • toString() : getMessage() + 어떤 Exception이 발생했는지 보여줍니다.
  • printStackTrace() : 어떤 Exception이 발생했는지와 에러가 발생한 위치까지 보여줍니다.

 

이제 본격적으로 오류와 예외를 각각 알아보겠습니다.

🔎 Error 오류

이중에서 대표적인 것들만 알아보겠습니다.

 

StackOverflowError

: 메서드 호출이 너무 많이 중첩되어 JVM 스택 메모리가 초과될 때 발생합니다. 이는 주로 재귀 호출이 깊어지거나 끝나지 않는 재귀로 인해 발생하게 됩니다.

public class StackOverflowExample {
    public static void recursivePrint(int num) {
        System.out.println("Number: " + num);
        if(num == 0)
            return; // 종료 조건
        else
            recursivePrint(++num); // 재귀 호출, 종료 조건 없이 계속 증가
    }

    public static void main(String[] args) {
        recursivePrint(1);
    }
}

 

 

OutOfMemoryError

: 프로그램이 사용할 수 있는 힙 메모리가 부족할 때 발생합니다. 객체 생성과 같은 메모리 요구가 사용 가능한 메모리를 초과할 때 발생합니다.

  • Garbage Collector에 의해 추가적인 메모리가 확보되지 못하는 상황일 때
  • heap 사이즈가 부족할 때
  • 너무 많은 class를 로드할 때
  • 큰 메모리의 native 메서드가 호출될 때
import java.util.ArrayList;
import java.util.List;

public class OutOfMemoryErrorExample {
    public static void main(String[] args) {
        List<Object> objects = new ArrayList<>();
        while (true) {
            objects.add(new Object()); // 계속 새로운 객체를 생성하고 리스트에 추가
        }
    }
}

 

앞서 설명했듯이 개발자가 미리 오류를 대체하기는 힘듭니다.

다만 StackOverflowError를 피하기 위해서 재귀를 사용할 때에 조심하거나 가시적인 loop를 사용하는 간접적인 예방이 가능합니다.

 

또한 OutOfMemoryError를 피하기 위해 새는 메모리를 차단하거나 heap의 크기를 늘려주는 방법을 사용할 수도 있습니다.

 

🔎 Exception 예외

예외는 개발자가 구현한 로직에서 발생하며 다른 방식으로 처리가능한 것들로, JVM은 정상 동작합니다.

 

🔎 Exception의 2가지 종류

 

Checked Exception

  • 예외처리가 필수이며, 처리하지 않으면 컴파일되지 않습니다. (try-catch로 감싸거나 throw로 던져서 예외처리합니다.)
  • 컴파일 단계에서 명확하게 Exception체크가 가능합니다.
  • 예외 발생시 트랜잭션을 roll-back 하지 않고 예외를 던져줍니다.

Unchecked Exception

  • RuntimeException 하위의 모든 예외(NullPointerException, IndexOutOfBoundException 등)
  • 런타임 시점에서 발생하는 Exception 입니다.
  • 명시적인 예외처리를 하지 않아도 됩니다.

🔎 Exception Handling

 

 

예외 복구

예외 복구는 발생한 예외를 적절히 처리하여 프로그램이 정상적으로 동작하도록 만드는 방법입니다. 이 방법은 예외가 발생했을 때 즉시 대응하여 문제를 해결합니다.

public class ExceptionRecoveryExample {
    public static void main(String[] args) {
        int[] numbers = {1, 2, 3};
        
        try {
            System.out.println(numbers[3]); // ArrayIndexOutOfBoundsException 발생
        } catch (ArrayIndexOutOfBoundsException e) {
            System.err.println("인덱스 범위를 벗어났습니다. 마지막 인덱스로 조정합니다.");
            System.out.println(numbers[numbers.length - 1]); // 마지막 요소 출력으로 복구
        }
    }
}

이렇게 배열의 범위를 벗어나는 접근이 발생할 때 예외를 잡아서 마지막 요소를 출력하도록 복구하고 있습니다.

 

예외 처리 회피

예외 처리 회피는 발생한 예외를 현재 메서드에서 처리하지 않고, 호출자 메서드로 예외를 전파하는 방식입니다. 이 방법은 'throws' 키워드를 사용하여 메서드 선언부에 예외를 명시합니다.

public class ExceptionPropagationExample {
    public static void main(String[] args) {
        try {
            readFile("path/to/file");
        } catch (IOException e) {
            System.err.println("파일 처리 중 오류 발생: " + e.getMessage());
        }
    }

    public static void readFile(String path) throws IOException {
        throw new IOException("파일을 읽을 수 없습니다.");
    }
}

readFile 메서드에서 IOException을 직접 처리하지 않고, 이를 호출하는 main 메서드로 예외를 전파하여 처리합니다.

 

예외 전환

예외 전환은 발생한 예외를 다른 예외로 변환하여 상위로 던지는 방법입니다. 쉽게 얘기하면 예외 처리 회피와 비슷하게 메서드 밖으로 예외를 던지지만, 무작정 던지지 않고 적절한 예외로 필터링해서 넘기는 방법입니다.

public class ExceptionTranslationExample {
    public static void main(String[] args) {
        try {
            processFile("path/to/file");
        } catch (CustomException e) {
            System.err.println("사용자 정의 예외 처리: " + e.getMessage());
        }
    }

    public static void processFile(String path) throws CustomException {
        try {
            readFile(path);
        } catch (IOException e) {
            throw new CustomException("파일 처리 중 문제 발생", e);
        }
    }

    public static void readFile(String path) throws IOException {
        throw new IOException("파일을 읽을 수 없습니다.");
    }
}

class CustomException extends Exception {
    public CustomException(String message, Throwable cause) {
        super(message, cause);
    }
}

 

profile

응애개발자

@Eungae-D

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!