본문 바로가기
■Development■/《Java》

[Java] throw와 throws의 차이

by 은스타 2024. 7. 24.
반응형

Java throw와 throws의 차이점 완벽 정리

안녕하세요
이번 포스팅에서는 Java 예외 처리를 공부하다 보면 throwthrows 키워드의 차이점에 대해 혼란스러울 때가 있습니다. 이름은 비슷하지만 완전히 다른 용도로 사용되는 이 두 키워드의 차이점과 활용법을 확실히 알아봅시다. 이 글을 읽고 나면 Java 예외 처리에 대한 이해도가 한층 깊어질 것입니다.


목차

  1. throw와 throws 개요
  2. throw 키워드 상세 분석
  3. throws 키워드 상세 분석
  4. throw와 throws의 핵심 차이점
  5. 예제로 배우는 throw와 throws
  6. 실무에서의 활용 패턴
  7. 자주 범하는 실수와 모범 사례
  8. 결론

#1. throw와 throws 개요

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

  • throw: 예외를 발생시키는 키워드
  • throws: 메서드가 예외를 선언하는 키워드

간단히 말해, throw는 실제로 예외를 던지는 행동을 할 때 사용하고, throws는 메서드가 어떤 예외를 발생시킬 수 있는지 명시할 때 사용합니다. 이제 각각에 대해 자세히 알아보겠습니다.

 

#2. throw 키워드 상세 분석

throw란?

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

기본 문법

throw 예외객체;

throw 특징

  1. 예외 인스턴스 생성: throw는 항상 예외 객체와 함께 사용됩니다.
  2. 메서드 내부에서 사용: 메서드 본문 내에서 사용되며, 실행 흐름 중에 발생합니다.
  3. 즉시 예외 발생: throw 문이 실행되면 즉시 현재 메서드의 실행이 중단되고 예외가 발생합니다.
  4. 단수형: 한 번에 하나의 예외만 발생시킬 수 있습니다.

throw 사용 예시

public void checkAge(int age) {
    if (age < 0) {
        throw new IllegalArgumentException("나이는 음수가 될 수 없습니다.");
    }
    if (age > 150) {
        throw new IllegalArgumentException("유효하지 않은 나이입니다.");
    }
    System.out.println("유효한 나이입니다: " + age);
}

이 예제에서는 나이가 유효하지 않은 범위에 있을 때 IllegalArgumentException을 발생시킵니다.

 

#3. throws 키워드 상세 분석

throws란?

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

기본 문법

반환타입 메서드명(매개변수) throws 예외클래스1, 예외클래스2, ... {
    // 메서드 본문
}

throws 특징

  1. 메서드 시그니처의 일부: 메서드 선언부에 작성되며, 메서드가 던질 수 있는 예외를 문서화합니다.
  2. 예외 전파: 메서드 내에서 발생한 예외를 직접 처리하지 않고 호출자에게 전파합니다.
  3. 복수형: 쉼표로 구분하여 여러 예외 유형을 선언할 수 있습니다.
  4. 컴파일러 검사: 체크 예외(Checked Exception)의 경우 컴파일러가 예외 처리 여부를 강제 검사합니다.

throws 사용 예시

public void readFile(String filePath) throws FileNotFoundException, IOException {
    File file = new File(filePath);
    FileInputStream fis = new FileInputStream(file); // FileNotFoundException 발생 가능

    byte[] data = new byte[100];
    fis.read(data); // IOException 발생 가능

    fis.close(); // IOException 발생 가능
}

이 예제에서는 메서드가 FileNotFoundExceptionIOException을 발생시킬 수 있음을 선언하고 있습니다.

 

#4. throw와 throws의 핵심 차이점

No 특성 throw throws
1 위치 메서드 내부 (구현부) 메서드 선언부 (시그니처)
2 목적 예외를 실제로 발생시킴 발생 가능한 예외를 선언
3 구문 throw new ExceptionType(); 메서드() throws ExceptionType1, ExceptionType2 { }
4 개수 한 번에 하나의 예외만 발생 가능 여러 예외 유형을 동시에 선언 가능
5 실행 시점 런타임 시 실행 컴파일 타임에 검사 (체크 예외의 경우)
6 예외 객체 반드시 예외 객체가 필요 예외 클래스명만 명시
7 필수 여부 필요에 따라 선택적 사용 체크 예외는 반드시 선언 필요

 

#5. 예제로 배우는 throw와 throws

실제 코드 예제를 통해 두 키워드의 사용법을 더 명확하게 이해해봅시다.

1. 기본 예제: throw와 throws의 협력

public class ExceptionExample {
    // throws로 메서드가 IOException을 발생시킬 수 있음을 선언
    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) {
        ExceptionExample example = new ExceptionExample();

        try {
            example.processFile("");
        } catch (IllegalArgumentException e) {
            System.out.println("인자 오류: " + e.getMessage());
        } catch (IOException e) {
            System.out.println("IO 오류: " + e.getMessage());
        }
    }
}

2. 사용자 정의 예외와 함께 사용하기

// 사용자 정의 예외 클래스
class InsufficientBalanceException extends Exception {
    public InsufficientBalanceException(String message) {
        super(message);
    }
}

class BankAccount {
    private double balance;

    public BankAccount(double initialBalance) {
        this.balance = initialBalance;
    }

    // throws로 메서드가 InsufficientBalanceException을 발생시킬 수 있음을 선언
    public void withdraw(double amount) throws InsufficientBalanceException {
        if (amount <= 0) {
            throw new IllegalArgumentException("출금액은 양수여야 합니다.");
        }

        if (amount > balance) {
            // throw로 실제 예외 발생
            throw new InsufficientBalanceException(
                "잔고 부족: 현재 잔고 " + balance + ", 요청 금액 " + amount);
        }

        balance -= amount;
        System.out.println("출금 완료: " + amount + ", 남은 잔고: " + balance);
    }
}

public class BankExample {
    public static void main(String[] args) {
        BankAccount account = new BankAccount(1000);

        try {
            account.withdraw(1500); // 잔고보다 큰 금액
        } catch (InsufficientBalanceException e) {
            System.out.println("오류: " + e.getMessage());
        }
    }
}

#6. 실무에서의 활용 패턴

1. 유효성 검사에서의 throw 활용

업무 로직에서 입력값 검증은 매우 중요합니다. throw를 활용하여 입력값이 유효하지 않을 때 적절한 예외를 발생시켜 프로그램의 안정성을 높일 수 있습니다.

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. API 계층에서의 throws 활용

외부 시스템과 통신하는 API 계층에서는 throws를 사용하여 발생 가능한 예외를 명확히 문서화하고 호출자에게 예외 처리 책임을 전달할 수 있습니다.

public interface UserService {
    User getUserById(long id) throws UserNotFoundException;
    void createUser(User user) throws DuplicateUserException, ValidationException;
    void updateUser(User user) throws UserNotFoundException, ValidationException;
    void deleteUser(long id) throws UserNotFoundException;
}

3. 예외 전환 패턴

하위 계층에서 발생한 예외를 상위 계층에 적합한 예외로 전환하는 패턴으로, 추상화 레벨을 유지하는 데 도움이 됩니다.

public void saveUserData(User user) throws ServiceException {
    try {
        userRepository.save(user);
    } catch (DataAccessException e) {
        // 하위 계층 예외를 상위 계층에 적합한 예외로 전환
        throw new ServiceException("사용자 데이터 저장 중 오류 발생", e);
    }
}

#7. 자주 범하는 실수와 모범 사례

자주 범하는 실수

  1. 예외 삼키기(Exception Swallowing)
// 나쁜 예
public void badExample() {
    try {
        riskyOperation();
    } catch (Exception e) {
        // 아무것도 하지 않음 - 예외를 "삼켜버림"
    }
}
  1. 과도한 throws 선언
// 나쁜 예
public void tooManyThrows() throws Exception, IOException, SQLException, NullPointerException {
    // 메서드 본문
}
  1. 불필요한 체크 예외 사용
// 나쁜 예
public void unnecessaryCheckedException() throws CustomCheckedException {
    if (someCondition) {
        throw new CustomCheckedException("회복 가능한 상황이 아님에도 체크 예외 사용");
    }
}

모범 사례

  1. 구체적인 예외 사용
// 좋은 예
public void specificException(String value) {
    if (value == null) {
        throw new NullPointerException("값이 null입니다.");
    } else if (value.isEmpty()) {
        throw new IllegalArgumentException("값이 비어 있습니다.");
    }
}
  1. 예외 메시지에 충분한 정보 포함
// 좋은 예
public void goodExceptionMessage(User user) {
    if (user == null) {
        throw new NullPointerException("사용자 객체가 null입니다.");
    }

    if (user.getId() <= 0) {
        throw new IllegalArgumentException(
            "유효하지 않은 사용자 ID: " + user.getId() + ", 양수여야 합니다.");
    }
}
  1. 자원 관리에 try-with-resources 사용
// 좋은 예
public String readFirstLine(String path) throws IOException {
    try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
        return reader.readLine();
    }
}
  1. 체크 예외와 언체크 예외 적절히 구분
// 좋은 예
// 복구 가능한 상황에는 체크 예외
public void processConfig() throws ConfigurationException {
    // 설정 파일 처리 로직
}

// 프로그래밍 오류에는 언체크 예외
public void validateInput(String input) {
    if (input == null) {
        throw new IllegalArgumentException("입력값이 null입니다.");
    }
}

 

#8. 결론

throwthrows는 Java 예외 처리 메커니즘의 핵심 키워드로, 각각 다른 목적과 사용법을 가지고 있습니다.

  • throw는 메서드 내부에서 예외를 직접 발생시켜 프로그램 흐름을 제어하는 데 사용됩니다.
  • throws는 메서드가 처리하지 않는 예외를 선언하여 호출자에게 예외 처리 책임을 전달하는 데 사용됩니다.

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

예외 처리 전략을 세울 때는 코드의 가독성, 유지보수성, 그리고 신뢰성을 모두 고려해야 합니다. 명확한 예외 계층 구조와 의미 있는 예외 메시지는 디버깅 시간을 크게 줄이고 코드 품질을 향상시킵니다.

여러분은 프로젝트에서 어떤 예외 처리 전략을 사용하고 계신가요? throwthrows를 사용하면서 겪었던 어려움이나 유용했던 패턴이 있다면 댓글로 공유해 주세요!


마무리

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

끝.

 

참고 자료

반응형