본문 바로가기
Development/Java

[Java] Java throw와 throws 차이점 비교 - 예외 처리 핵심 정리

by 은스타 2024. 7. 24.
반응형
Java throw와 throws 차이점 비교 - 예외 처리 핵심 정리

Java throw와 throws 차이점 비교

Java에서 예외 처리를 공부하다 보면 throw와 throws 키워드의 차이점에 대해 혼란스러울 때가 많습니다. 이름은 비슷하지만 완전히 다른 용도로 사용되는 이 두 키워드의 차이점과 실무 활용법을 명확하게 정리했습니다. 예외 처리 메커니즘의 핵심인 throw와 throws의 개념부터 실전 예제까지, 이 글 하나로 완벽하게 이해할 수 있습니다.
목차
1. throw와 throws 기본 개념
2. throw 키워드 완전 분석
3. throws 키워드 완전 분석
4. throw와 throws 핵심 차이점
5. 자주 묻는 질문 (FAQ)


#1. throw와 throws 기본 개념

Java에서 예외 처리는 프로그램의 안정성과 견고함을 보장하는 중요한 메커니즘입니다. throw와 throws는 이러한 예외 처리 메커니즘의 핵심 키워드지만, 그 역할과 사용법은 완전히 다릅니다.

1) 핵심 구분

throw는 예외를 직접 발생시키는 키워드이고, throws는 메서드가 예외를 선언하는 키워드입니다. 간단히 말해, throw는 실제로 예외를 던지는 행동을 할 때 사용하고, throws는 메서드가 어떤 예외를 발생시킬 수 있는지 명시할 때 사용합니다.

. . . . .
2) 사용 위치 비교

두 키워드의 가장 큰 차이는 사용 위치입니다. throw는 메서드 내부(구현부)에서 사용되며, throws는 메서드 선언부(시그니처)에서 사용됩니다. 이러한 위치의 차이는 각각의 역할을 명확하게 보여줍니다.

// throw 사용 예시 - 메서드 내부에서 예외 발생
public void checkAge(int age) {
    if (age < 0) {
        throw new IllegalArgumentException("나이는 음수가 될 수 없습니다.");
    }
}

// throws 사용 예시 - 메서드 선언부에서 예외 명시
public void readFile(String path) throws IOException {
    FileInputStream fis = new FileInputStream(path);
    fis.close();
}

#2. throw 키워드 완전 분석
1) throw의 정의와 역할

throw는 예외 객체를 생성하고 발생시키는 데 사용되는 키워드입니다. 프로그램 실행 중 특정 조건이 만족될 때 의도적으로 예외를 발생시키고 싶을 때 사용합니다.

(1) 기본 문법
throw 예외객체;

예외객체는 Throwable 클래스 또는 그 하위 클래스의 인스턴스여야 합니다. 일반적으로 Exception 또는 RuntimeException을 상속받은 클래스를 사용합니다.

. . . . .
2) throw의 주요 특징
(1) 예외 인스턴스 생성 필수

throw는 항상 예외 객체와 함께 사용됩니다. 예외 클래스명만으로는 사용할 수 없으며, 반드시 new 키워드로 객체를 생성해야 합니다.

(2) 메서드 내부 사용

throw는 메서드 본문 내에서 사용되며, 실행 흐름 중에 발생합니다. 조건문과 함께 사용되어 특정 조건에서만 예외를 발생시키는 패턴이 일반적입니다.

(3) 즉시 예외 발생

throw 문이 실행되면 즉시 현재 메서드의 실행이 중단되고 예외가 발생합니다. throw 문 이후의 코드는 실행되지 않습니다.

(4) 단수형 특성

한 번에 하나의 예외만 발생시킬 수 있습니다. 여러 예외를 동시에 발생시킬 수는 없습니다.

. . . . .
3) throw 실전 예제
(1) 유효성 검사
public void registerUser(String username, String email, int age) {
    // 사용자 이름 검증
    if (username == null || username.trim().isEmpty()) {
        throw new IllegalArgumentException("사용자 이름은 필수입니다.");
    }
    
    // 이메일 검증
    if (email == null || !email.contains("@")) {
        throw new IllegalArgumentException("유효한 이메일 주소가 아닙니다.");
    }
    
    // 나이 검증
    if (age < 13) {
        throw new IllegalArgumentException("사용자는 13세 이상이어야 합니다.");
    }
    
    // 사용자 등록 로직...
}
(2) 사용자 정의 예외 발생
// 사용자 정의 예외 클래스
class InsufficientBalanceException extends Exception {
    public InsufficientBalanceException(String message) {
        super(message);
    }
}

// 사용자 정의 예외 발생
public void withdraw(double amount) throws InsufficientBalanceException {
    if (amount > balance) {
        throw new InsufficientBalanceException(
            "잔고 부족: 현재 " + balance + ", 요청 " + amount);
    }
    balance -= amount;
}

#3. throws 키워드 완전 분석
1) throws의 정의와 역할

throws는 메서드 선언부에 사용되어 해당 메서드가 발생시킬 수 있는 예외 유형을 명시합니다. 이는 메서드를 호출하는 코드에게 어떤 예외를 처리해야 하는지 알려주는 역할을 합니다.

(1) 기본 문법
반환타입 메서드명(매개변수) throws 예외클래스1, 예외클래스2, ... {
    // 메서드 본문
}
. . . . .
2) throws의 주요 특징
(1) 메서드 시그니처의 일부

throws는 메서드 선언부에 작성되며, 메서드가 던질 수 있는 예외를 문서화합니다. 이는 API 사용자에게 중요한 정보를 제공합니다.

(2) 예외 전파 메커니즘

메서드 내에서 발생한 예외를 직접 처리하지 않고 호출자에게 전파합니다. 이를 통해 예외 처리 책임을 상위 레벨로 위임할 수 있습니다.

(3) 복수형 선언 가능

쉼표로 구분하여 여러 예외 유형을 동시에 선언할 수 있습니다. 이는 throw와의 중요한 차이점입니다.

(4) 컴파일러 검사

체크 예외(Checked Exception)의 경우 컴파일러가 예외 처리 여부를 강제 검사합니다. throws로 선언하지 않으면 컴파일 에러가 발생합니다.

. . . . .
3) throws 실전 예제
(1) 파일 처리
public void readFile(String filePath) throws FileNotFoundException, IOException {
    File file = new File(filePath);
    // FileNotFoundException 발생 가능
    FileInputStream fis = new FileInputStream(file);
    
    byte[] data = new byte[100];
    // IOException 발생 가능
    fis.read(data);
    fis.close();
}
(2) API 계층 설계
public interface UserService {
    // 여러 예외를 선언하여 명확한 API 제공
    User getUserById(long id) throws UserNotFoundException;
    
    void createUser(User user)
        throws DuplicateUserException, ValidationException;
    
    void updateUser(User user)
        throws UserNotFoundException, ValidationException;
}
(3) 예외 전환 패턴
public void saveUserData(User user) throws ServiceException {
    try {
        userRepository.save(user);
    } catch (DataAccessException e) {
        // 하위 예외를 상위 예외로 전환
        throw new ServiceException("데이터 저장 실패", e);
    }
}

#4. throw와 throws 핵심 차이점
1) 비교표로 보는 핵심 차이
구분 throw throws
사용 위치 메서드 내부 (구현부) 메서드 선언부 (시그니처)
목적 예외를 실제로 발생시킴 발생 가능한 예외를 선언
구문 throw new Exception(); method() throws Exception1, Exception2
예외 개수 한 번에 하나만 발생 가능 여러 예외 유형 동시 선언 가능
실행 시점 런타임 시 실행 컴파일 타임에 검사
예외 객체 반드시 예외 객체 필요 예외 클래스명만 명시
필수 여부 필요에 따라 선택적 사용 체크 예외는 반드시 선언 필요
. . . . .
2) 협력 관계 예제

throw와 throws는 서로 협력하여 완전한 예외 처리 메커니즘을 구성합니다. 다음 예제는 두 키워드가 함께 작동하는 방식을 보여줍니다.

// throws로 예외를 선언하고, throw로 실제 발생
public void processFile(String path) throws IOException {
    if (path == null || path.isEmpty()) {
        // throw로 실제 예외 발생
        throw new IllegalArgumentException("경로가 유효하지 않습니다.");
    }
    
    File file = new File(path);
    if (!file.exists()) {
        // throw로 실제 예외 발생
        throw new FileNotFoundException("파일을 찾을 수 없습니다: " + path);
    }
    
    // 파일 처리 로직...
}

// 호출하는 쪽에서 예외 처리
public static void main(String[] args) {
    try {
        processFile("example.txt");
    } catch (IllegalArgumentException e) {
        System.out.println("인자 오류: " + e.getMessage());
    } catch (IOException e) {
        System.out.println("IO 오류: " + e.getMessage());
    }
}
. . . . .
3) 실무 활용 패턴
(1) 체크 예외와 언체크 예외 구분

복구 가능한 상황에는 체크 예외를 사용하고 throws로 선언하며, 프로그래밍 오류에는 언체크 예외를 사용하고 throw로 발생시킵니다.

// 복구 가능한 상황 - 체크 예외 + throws
public void processConfig() throws ConfigurationException {
    // 설정 파일 처리 로직
}

// 프로그래밍 오류 - 언체크 예외 + throw
public void validateInput(String input) {
    if (input == null) {
        throw new IllegalArgumentException("입력값이 null입니다.");
    }
}
(2) 예외 메시지 작성 모범 사례

예외를 발생시킬 때는 충분한 정보를 포함한 메시지를 작성해야 디버깅이 용이합니다.

// 나쁜 예
throw new IllegalArgumentException("오류");

// 좋은 예
throw new IllegalArgumentException(
    "유효하지 않은 사용자 ID: " + userId + ", 양수여야 합니다.");

#5. 자주 묻는 질문 (FAQ)
1) Q: throw와 throws 중 어느 것을 먼저 사용해야 하나요?

A: 순서가 정해진 것은 아니지만, 일반적으로 throws를 먼저 선언하고 throw를 사용합니다. throws는 메서드 시그니처의 일부이므로 메서드 설계 단계에서 결정되고, throw는 구현 단계에서 실제 예외를 발생시킬 때 사용됩니다.

. . . . .
2) Q: RuntimeException도 throws로 선언해야 하나요?

A: RuntimeException과 그 하위 클래스는 언체크 예외이므로 throws로 선언할 필요가 없습니다. 컴파일러가 강제하지 않으며, 선언하지 않아도 컴파일 에러가 발생하지 않습니다. 다만 명시적으로 선언하여 문서화할 수는 있습니다.

. . . . .
3) Q: 여러 예외를 한 번에 throw할 수 있나요?

A: 아니요, throw는 한 번에 하나의 예외만 발생시킬 수 있습니다. 여러 예외를 처리해야 하는 경우에는 조건에 따라 다른 throw 문을 사용하거나, 예외를 연결(chaining)하는 방법을 사용할 수 있습니다.

. . . . .
4) Q: throws Exception으로 모든 예외를 선언하면 안 되나요?

A: 기술적으로는 가능하지만 좋은 관행이 아닙니다. 구체적인 예외를 선언하는 것이 API 사용자에게 더 명확한 정보를 제공합니다. Exception으로 선언하면 어떤 예외가 발생할 수 있는지 알 수 없어 적절한 처리가 어렵습니다.

. . . . .
5) Q: try-catch 없이 throw만 사용해도 되나요?

A: 네, 가능합니다. throw로 발생시킨 예외는 현재 메서드에서 처리하지 않고 호출자에게 전파할 수 있습니다. 이 경우 해당 메서드는 throws로 예외를 선언해야 합니다(체크 예외의 경우).

. . . . .
6) Q: throw new Exception()과 throw e의 차이는 무엇인가요?

A: throw new Exception()은 새로운 예외 객체를 생성하여 발생시키고, throw e는 기존 예외 객체를 다시 발생시킵니다. 기존 예외를 다시 던질 때는 스택 트레이스가 보존되므로 디버깅에 유리합니다.

. . . . .
7) Q: throws를 사용하면 반드시 try-catch로 처리해야 하나요?

A: 체크 예외의 경우, 호출하는 메서드에서 try-catch로 처리하거나 다시 throws로 선언해야 합니다. 언체크 예외는 강제되지 않습니다. 예외 처리 책임을 어느 레벨에서 수행할지 설계하는 것이 중요합니다.

. . . . .
8) Q: 생성자에서도 throws를 사용할 수 있나요?

A: 네, 생성자도 메서드와 마찬가지로 throws를 사용할 수 있습니다. 생성자에서 예외가 발생하면 객체 생성이 실패하고, 호출자는 이를 처리해야 합니다.

public class FileProcessor {
    // 생성자에서 throws 사용
    public FileProcessor(String path) throws IOException {
        File file = new File(path);
        if (!file.exists()) {
            throw new FileNotFoundException("파일 없음: " + path);
        }
    }
}
. . . . .
9) Q: 예외를 삼키는(swallowing) 것은 왜 나쁜가요?

A: 예외를 catch 블록에서 아무 처리도 하지 않고 무시하는 것을 "예외 삼키기"라고 합니다. 이는 문제를 숨기고 디버깅을 어렵게 만듭니다. 최소한 로그라도 남겨야 문제 파악이 가능합니다.

// 나쁜 예 - 예외 삼키기
try {
    riskyOperation();
} catch (Exception e) {
    // 아무것도 하지 않음
}

// 좋은 예
try {
    riskyOperation();
} catch (Exception e) {
    logger.error("작업 실패: " + e.getMessage(), e);
    throw new ServiceException("처리 실패", e);
}
. . . . .
10) Q: try-with-resources와 throws를 함께 사용할 수 있나요?

A: 네, try-with-resources는 자원을 자동으로 닫아주는 기능이며, throws와는 독립적입니다. 자원 관리와 예외 처리를 모두 효과적으로 수행할 수 있습니다.

public String readFirstLine(String path) throws IOException {
    // try-with-resources와 throws 함께 사용
    try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
        return reader.readLine();
    }
    // reader는 자동으로 close됨
}

마무리

throw와 throws는 Java 예외 처리 메커니즘의 핵심 키워드로, 각각 명확히 다른 목적과 사용법을 가지고 있습니다. throw는 메서드 내부에서 예외를 직접 발생시켜 프로그램 흐름을 제어하는 데 사용되고, throws는 메서드가 처리하지 않는 예외를 선언하여 호출자에게 예외 처리 책임을 전달하는 데 사용됩니다.

이 두 키워드를 적절히 활용하면 더 안정적이고 유지보수하기 쉬운 Java 애플리케이션을 개발할 수 있습니다. 예외는 단순히 오류 상황을 처리하는 메커니즘이 아니라, 프로그램의 설계와 품질에 영향을 미치는 중요한 요소입니다.

실무에서는 다음 원칙을 기억하시기 바랍니다. 구체적인 예외를 사용하고, 충분한 정보를 포함한 예외 메시지를 작성하며, 예외를 삼키지 말고, 복구 가능한 상황에는 체크 예외를, 프로그래밍 오류에는 언체크 예외를 사용하십시오.

긴 글 읽어주셔서 감사합니다.

끝.
반응형