Java 예외 처리 마스터하기: throw와 throws 완전 가이드

目次

1. Introduction

Java 프로그래밍을 시작하면 필연적으로 “예외 처리(exception handling)”라는 용어를 접하게 됩니다. 여러 키워드 중에서도 “throw”와 “throws”는 모양은 비슷하지만 용도가 달라 초보자에게 특히 혼란을 줍니다.

Java는 안전성과 견고함을 염두에 두고 설계된 언어이며, 오류와 예상치 못한 상황을 적절히 처리하기 위한 내장 메커니즘을 제공합니다. 이 메커니즘을 “예외 처리”라고 합니다. 예외 처리는 프로그램의 신뢰성과 유지보수성을 크게 향상시키는 핵심 역할을 합니다.

본 문서에서는 “java throws”의 사용법에 초점을 맞추어, 예외 처리의 기본 개념부터 시작해 자주 묻는 질문과 흔히 저지르는 실수까지 다룹니다. “throw”와 “throws”의 차이를 잘 모르는 분이나 throws를 언제, 어떻게 활용해야 할지 궁금한 분들에게 특히 유용한 가이드가 될 것입니다. 실제 프로젝트에서 흔히 볼 수 있는 실용적인 정보, 팁, 샘플 코드를 포함했으니 끝까지 읽어 보시기 바랍니다.

2. What Is Exception Handling in Java?

Java 프로그램을 작성하다 보면 런타임 시 다양한 예상치 못한 상황이 발생할 수 있습니다. 예를 들어 파일을 찾을 수 없거나, 0으로 나누는 오류가 발생하거나, 배열의 범위를 벗어난 접근을 시도하는 경우가 있습니다. 이러한 상황을 “예외(exception)”라고 부릅니다.

2.1 Basic Concepts of Exception Handling

예외 처리는 프로그램 실행 중 발생하는 비정상적인 상황(예외)을 감지하고, 개발자가 적절히 대응할 수 있도록 하는 메커니즘입니다. 예외가 발생했을 때 프로그램이 갑자기 종료되는 대신, Java는 오류의 유형과 내용에 따라 의미 있는 방식으로 응답할 수 있게 해줍니다. 이를 통해 애플리케이션의 안정성과 사용자 경험이 향상됩니다.

2.2 Checked Exceptions and Unchecked Exceptions

Java 예외는 크게 두 가지 범주로 나뉩니다.

Checked Exceptions

Checked 예외는 컴파일 시점에 반드시 처리해야 하는 예외입니다. 파일 작업 중 발생하는 IOException이 대표적인 예입니다. 이러한 예외는 try‑catch 블록으로 잡아야 하거나, throws 선언을 통해 호출자에게 전파해야 합니다.

try {
    FileReader fr = new FileReader("data.txt");
} catch (IOException e) {
    e.printStackTrace();
}

Unchecked Exceptions

Unchecked 예외는 컴파일 시점에 강제적으로 처리할 필요가 없는 예외입니다. NullPointerException이나 ArrayIndexOutOfBoundsException처럼 주로 프로그래밍 실수에서 발생합니다. Java는 이러한 예외를 명시적으로 처리하지 않아도 컴파일이 되지만, 필요에 따라 처리하지 않으면 예상치 못한 오류가 발생할 수 있으므로 적절히 다루는 것이 권장됩니다.

2.3 Why Exception Handling Is Necessary

예외 처리를 적절히 구현하면 다음과 같은 장점이 있습니다:

  • 프로그램 안정성 향상: 예상치 못한 오류가 발생해도 프로그램이 적절한 메시지를 표시하거나 복구 로직을 실행하여 강제 종료를 방지합니다.
  • 디버깅 용이: 예외 유형과 메시지를 통해 문제 원인을 빠르게 파악할 수 있습니다.
  • 사용자 경험 개선: 오류로 인해 갑자기 종료되는 대신, 시스템이 의미 있는 피드백이나 복구 단계를 제공할 수 있습니다.

Java에서 예외 처리는 견고한 애플리케이션을 구축하기 위한 필수 기술입니다. 다음 장에서는 “throw”의 기본에 대해 설명합니다.

3. What Is throw?

Java에서 “throw”는 예외를 의도적으로 발생시킬 때 사용하는 키워드입니다. 예외는 프로그램 실행 중 자동으로 발생하는 경우가 많지만, 특정 조건이 만족될 때 직접 예외를 만들고 발생시키고 싶을 때 “throw”를 사용합니다.

3.1 Basic Usage of throw

“throw”는 예외 객체를 명시적으로 생성하고 이를 던져 예외가 발생하도록 합니다. 기본 문법은 다음과 같습니다:

throw new ExceptionClass("Error message");

예를 들어, 잘못된 인수가 전달된 경우 다음과 같이 예외를 발생시킬 수 있습니다.

public void setAge(int age) {
    if (age < 0) {
        throw new IllegalArgumentException("Age must be zero or greater");
    }
    this.age = age;
}

이 예제에서는 나이가 0보다 작을 때 IllegalArgumentException이 발생합니다.

3.2 예외를 발생시켜야 할 수 있는 이유

“throw”를 사용하는 주요 목적은 프로그램에 잘못된 상태나 규칙 위반을 즉시 알리는 것입니다. 이는 버그를 조기에 발견하고 의도치 않은 동작을 방지하는 데 도움이 됩니다.

예시에는 다음이 포함됩니다:

  • 사용자가 입력한 값이 검증에 실패할 때
  • 잘못된 매개변수나 설정이 전달될 때
  • 비즈니스 로직이 추가 처리를 방해할 때

3.3 throw 사용 시 주의사항

“throw”를 사용해 예외를 발생시키면, 같은 메서드 내에서 try‑catch 블록으로 처리하지 않는 한 호출자에게 전파됩니다. 체크 예외(IOException 등)의 경우, 메서드 시그니처에 “throws”를 선언해야 합니다. 언체크 예외의 경우 throws 선언은 선택 사항이지만, “throw”와 “throws”의 차이를 이해하는 것이 올바른 사용을 위해 필수적입니다.

4. throws란 무엇인가?

Java 프로그램을 작성할 때 메서드 선언에 “throws” 키워드를 볼 수 있습니다. throws 키워드는 메서드 실행 중 하나 이상의 예외가 발생할 수 있음을 호출자에게 알리기 위해 사용됩니다.

4.1 throws의 기본 사용법

메서드 선언에 예외 클래스 이름을 지정하면, throws 키워드는 메서드 내부에서 발생할 수 있는 모든 예외를 호출자에게 전파합니다. 특히 체크 예외는 호출자가 올바르게 처리하도록 throws로 선언해야 합니다.

public void readFile(String path) throws IOException {
    FileReader reader = new FileReader(path);
    // File reading process
}

이 예제에서 FileReader 생성자는 IOException을 발생시킬 수 있으므로, 메서드는 throws IOException를 선언해야 합니다.

4.2 메서드 선언에서의 예외 전파

메서드가 throws를 선언하면, 내부에서 발생하는 모든 예외가 호출자에게 전파됩니다. 호출자는 예외를 잡거나 자체적으로 throws를 선언해 다시 전파해야 합니다.

public void processFile() throws IOException {
    readFile("test.txt"); // readFile throws IOException, so this method must also declare throws
}

4.3 여러 예외 선언하기

메서드가 여러 예외를 발생시킬 수 있다면, throws 키워드 뒤에 콤마로 구분된 목록으로 선언할 수 있습니다.

public void connect(String host) throws IOException, SQLException {
    // Network or database operations
}

4.4 throws의 역할과 장점

  • 가독성 및 유지보수성 향상: throws 선언을 통해 메서드가 어떤 예외를 발생시킬 수 있는지 즉시 파악할 수 있어 개발자 간 커뮤니케이션이 개선됩니다.
  • 오류 처리 책임 명확화: throws는 호출자가 예외를 반드시 처리하도록 하여 견고하고 구조화된 시스템 설계를 촉진합니다.
  • 커스텀 예외 지원: 개발자는 throws 선언에 사용자 정의 예외 클래스를 포함시켜 복잡한 오류 상황을 보다 효과적으로 처리할 수 있습니다.

5. throw와 throws의 차이점

종종 혼동되지만, “throw”와 “throws”는 Java 예외 처리 메커니즘에서 매우 다른 역할을 합니다. 이 장에서는 두 키워드의 차이를 명확히 하고 각각을 언제, 어떻게 올바르게 사용해야 하는지 설명합니다.

5.1 throw와 throws의 기능적 차이

Itemthrowthrows
RoleActually generates an exceptionDeclares that a method may throw exceptions
UsageUsed inside methods to throw exception objectsUsed in method declarations to specify throwable exceptions
TargetException objects created with newBoth checked and unchecked exceptions
Examplethrow new IOException(“Error occurred”);public void sample() throws IOException
When requiredWhen intentionally raising an exceptionWhen a method may throw checked exceptions

5.2 각각이 사용되는 상황

  • throw
  • 예외를 직접 발생시키고자 할 때 사용합니다—예를 들어, 잘못된 입력이나 규칙 위반을 감지했을 때.
  • 예시: “나이가 0보다 작으면 IllegalArgumentException을 throw한다.”
  • throws
  • 메서드나 생성자가 예외를 발생시킬 수 있으며 호출자에게 이를 알려야 할 때 사용합니다.
  • 예시: “파일 작업이나 데이터베이스 접근과 같이 예외가 예상되는 메서드에 throws를 사용한다.”

5.3 비교를 위한 코드 예시

throw 예시:

public void setName(String name) {
    if (name == null || name.isEmpty()) {
        throw new IllegalArgumentException("Name cannot be empty");
    }
    this.name = name;
}

throws 예시:

public void loadConfig(String path) throws IOException {
    FileReader reader = new FileReader(path);
    // Configuration loading process
}

5.4 요약 표

Decision Pointthrowthrows
Where it’s usedInside a methodMethod declaration
What it doesGenerates an exceptionDeclares exception propagation
Who handles itThrown at the point of errorHandled by the caller
When requiredOptional (only when needed)Required for checked exceptions

throw와 throws의 역할은 명확히 구분됩니다. 따라서 어떤 상황에서 어느 것을 사용해야 하는지를 이해하는 것이 견고한 예외 처리의 첫 번째 단계입니다.

6. throws 사용을 위한 모범 사례

throws를 효과적으로 사용하면 Java 프로그램의 가독성과 유지보수성이 향상되고, 예외 처리 전반의 품질도 높아집니다. 이 장에서는 실제 개발에서 흔히 사용되는 권장 실천 방법과 중요한 고려 사항을 소개합니다.

6.1 구체적인 예외 클래스 지정

throws 선언에서는 가능한 한 구체적인 예외 클래스를 지정해야 합니다.
Exception이나 Throwable을 광범위하게 선언하는 것을 피하십시오.
IOException이나 SQLException과 같은 구체적인 예외를 사용하면 호출자가 오류를 어떻게 처리해야 할지 정확히 판단할 수 있습니다.

올바른 예시:

public void saveData() throws IOException {
    // File-saving process
}

피해야 할 예시:

public void saveData() throws Exception {
    // Too vague: unclear what exceptions may occur
}

6.2 예외 계층 구조 활용

Java 예외 클래스는 계층 구조를 이루고 있기 때문에, 관련된 예외들을 적절히 상위 클래스 아래에 그룹화할 수 있습니다.
하지만 Exception과 같은 상위 수준의 예외를 과도하게 일반화하면 명확성이 떨어지고 오류 처리가 어려워집니다.

6.3 Javadoc에서 @throws 태그 사용

API나 라이브러리를 제공할 때는 Javadoc 주석에 @throws 태그를 사용해 예외를 문서화해야 합니다.
이렇게 하면 예외가 발생하는 조건을 명확히 설명하여 API 사용자가 올바른 예외 처리를 구현하는 데 도움이 됩니다.

/**
 * Reads a file.
 * @param filePath Path of the file to read
 * @throws IOException If the file cannot be read
 */
public void readFile(String filePath) throws IOException {
    // ...
}

6.4 불필요한 예외 재throw 방지

가치를 추가하지 않고 예외를 잡은 뒤 다시 throw하는 것을 피하십시오.
재throw이 필요할 경우, 원래 예외를 사용자 정의 예외로 감싸거나 추가 컨텍스트 또는 로그 정보를 포함하십시오.

6.5 사용자 정의 예외 클래스 사용

비즈니스 애플리케이션 및 대규모 시스템에서는 사용자 정의 예외 클래스를 정의하고 throws 선언에 포함하는 것이 일반적입니다.
이는 오류 원인과 책임을 명확히 하여 시스템을 보다 쉽게 유지보수하고 확장할 수 있게 합니다.

public class DataNotFoundException extends Exception {
    public DataNotFoundException(String message) {
        super(message);
    }
}

public void findData() throws DataNotFoundException {
    // Throw when data is not found
}

throws를 적절히 사용하면 예외 처리 책임을 분산시키고, 문제 해결을 단순화하며, 신뢰성과 보안성이 높은 Java 애플리케이션을 구축할 수 있습니다.

7. 실용적인 예외 처리 패턴

Java의 예외 처리는 단순한 try-catch 블록이나 throws 선언을 넘어섭니다.
이 장에서는 실제 개발에서 흔히 사용되는 실용적인 패턴과 설계 전략을 소개합니다.

7.1 try-with-resources를 활용한 자원 관리

파일, 네트워크 연결, 데이터베이스 연결 등을 다룰 때는 예외가 발생하더라도 자원을 적절히 해제하는 것이 중요합니다.
Java 7부터는 try-with-resources 구문을 사용하면 자원을 자동으로 닫을 수 있습니다.

try (FileReader reader = new FileReader("data.txt")) {
    // File reading process
} catch (IOException e) {
    System.out.println("Failed to read file: " + e.getMessage());
}

이 구문은 close()가 자동으로 호출되도록 보장하여, 예외가 발생하더라도 리소스 누수를 방지합니다.

7.2 여러 예외를 효율적으로 처리하기

복잡한 작업은 여러 종류의 예외를 발생시킬 수 있습니다. Java 7부터는 다중 캐치(multi‑catch) 기능을 사용하여 하나의 catch 절에서 여러 예외를 동시에 잡을 수 있습니다.

try {
    methodA();
    methodB();
} catch (IOException | SQLException e) {
    // Handle both exceptions here
    e.printStackTrace();
}

각 예외 유형에 맞는 맞춤형 처리를 제공하기 위해 catch 블록을 별도로 나눌 수도 있습니다.

7.3 예외 처리 성능 고려사항

예외는 강력하지만 일반적인 흐름 제어를 대신해서는 안 됩니다. 예외를 발생시키면 스택 트레이스를 생성해야 하므로 상당한 오버헤드가 발생합니다. 따라서 예외는 정말 비정상적인 경우에만 사용해야 합니다.

잘못된 사용 예 (권장되지 않음):

try {
    int value = array[index];
} catch (ArrayIndexOutOfBoundsException e) {
    // Bounds checking should be done beforehand
}

권장 사용 예:

if (index >= 0 && index < array.length) {
    int value = array[index];
} else {
    // Out-of-range handling
}

7.4 로깅 및 알림

예외가 발생했을 때 문제를 해결하기 위해서는 적절한 로깅과 알림이 필수적입니다. 기업 시스템에서는 보통 Log4j, SLF4J와 같은 로깅 프레임워크를 사용해 상세한 예외 정보를 기록합니다.

catch (Exception e) {
    logger.error("An error has occurred", e);
}

7.5 사용자 정의 복구 로직 구현

경우에 따라 작업을 재시도하거나, 설정 파일을 다시 로드하거나, 사용자에게 알리는 등 복구 로직을 구현하는 것이 유용합니다. 프로그램을 즉시 종료하기보다는 가능한 한 서비스 연속성을 유지하도록 노력해야 합니다.

실용적인 예외 처리 기법을 적용하면 신뢰성과 유지보수성이 모두 뛰어난 Java 애플리케이션을 만들 수 있습니다.

8. 자주 묻는 질문 (FAQ)

아래는 Java 예외 처리, 특히 “throws”와 관련해 초보자들이 자주 묻는 질문과 그에 대한 답변입니다.

Q1. throw와 throws의 주요 차이점은 무엇인가요?

A1.
throw는 프로그램 실행 중 실제로 예외를 생성하는 키워드입니다.
throws는 메서드 선언에서 해당 메서드가 예외를 발생시킬 수 있음을 알리기 위해 사용됩니다.
→ 기억하기 쉬운 방법: throw = “실행”, throws = “선언”.

Q2. throws를 사용할 때 주의해야 할 점은 무엇인가요?

A2.
throws로 선언된 예외는 호출자가 반드시 잡거나(catch) 다시 throws를 통해 전파해야 합니다.
체크 예외(checked exception)의 경우 명시적인 처리가 필수이며, 예외를 잡거나 전파하지 않으면 프로그램이 컴파일되지 않습니다.

Q3. throw와 throws를 함께 사용할 수 있나요?

A3.
예, 가능합니다. 흔히 사용하는 패턴은 메서드 내부에서 throw로 예외를 발생시키고, 같은 예외를 throws 선언에 포함시켜 호출자에게 전파하는 방식입니다.

Q4. throws를 사용해 여러 예외를 선언하려면 어떻게 하나요?

A4.
throws 키워드 뒤에 예외 타입을 콤마(,)로 구분하여 나열하면 됩니다.
예시: public void sample() throws IOException, SQLException

Q5. unchecked 예외에 throws를 사용해야 할까요?

A5.
RuntimeException을 상속받는 unchecked 예외는 throws 선언이 필요하지 않습니다.
하지만 메서드가 특정 unchecked 예외를 발생시킬 수 있음을 명시적으로 알리고 싶을 때는 throws를 사용해 가독성과 API 명확성을 높일 수 있습니다.

Q6. throws 절에 Exception이나 Throwable을 선언해도 괜찮나요?

A6.
기술적으로는 가능하지만 권장되지 않습니다. 너무 포괄적인 예외 타입을 선언하면 어떤 오류가 발생할 수 있는지 파악하기 어려워지고, 호출자 입장에서 적절한 처리를 하기가 힘들어집니다. 가능한 한 구체적인 예외 클래스를 사용하세요.

Q7. throws에 선언된 예외를 항상 잡아야 하나요?

A7.
체크된 예외의 경우, 호출자는 예외를 잡거나 throws를 사용해 더 이상 전파해야 합니다.
이를 수행하지 않으면 컴파일 오류가 발생합니다.
언체크 예외는 이 중 어느 것도 요구되지 않습니다.

Q8. throws를 쓰는 것을 잊으면 어떻게 되나요?

A8.
메서드가 체크된 예외를 발생시키지만 throws로 선언하지 않으면 컴파일 시 오류가 발생합니다.
언체크 예외의 경우, throws 없이도 메서드가 정상적으로 컴파일되지만, 적절한 오류 처리를 여전히 구현해야 합니다.

이 FAQ 섹션을 활용하여 Java 예외 처리에 대한 이해를 심화하세요.