Java 프로그래밍 언어는 오류와 그 밖의 예외적인 상황을 처리하기 위해 _예외_를 사용해요.
예외는 프로그램이 실행되는 동안 발생해서 명령의 정상적인 흐름을 방해하는 사건이에요. Java에서는 예외를 명시적으로 발생시키는데, 이렇게 예외를 발생시키는 행위를 _예외를 던진다_고 해요. 예외를 처리하는 행위는 _예외를 잡는다_고 하고요.
Java에서 모든 예외는 Exception 클래스의 하위 클래스이고, Exception은 다시 Throwable의 하위 클래스예요.
Java는 예외를 두 가지 종류로 구분해요:
_검사 예외_는 애플리케이션이 미리 예상하고 복구할 수 있어야 하는 예외적인 상황이에요.
검사 예외의 한 예로 FileNotFoundException이 있는데, 이는 메서드가 존재하지 않는 파일을 읽으려 할 때 발생해요.
이 유형의 예외는 컴파일 시점에 검사돼요. 검사 예외를 던지는 메서드는 메서드 시그니처에 이를 명시해야 하고, 검사 예외를 던질 수 있는 메서드를 호출하는 코드는 예외를 처리해야 하며, 그러지 않으면 컴파일되지 않아요.
모든 검사 예외는 Exception의 하위 클래스 중에서 RuntimeException을 상속하지 않는 것들이에요.
_비검사 예외_는 애플리케이션이 보통 예상하거나 복구할 수 없는 예외적인 상황이에요.
비검사 예외의 한 예로 NullPointerException이 있는데, 이는 null이 아닌 값을 기대하는 메서드가 null을 받을 때 발생해요.
이 유형의 예외는 컴파일 시점에 검사되지 않아요. 비검사 예외를 던지는 메서드는 메서드 시그니처에 이를 명시할 필요가 없고, 비검사 예외를 던질 수 있는 메서드를 호출하는 코드도 예외를 처리할 필요가 없어요.
모든 비검사 예외는 RuntimeException을 상속하고, RuntimeException은 다시 Exception을 확장한 것이에요.
Java의 메서드는 throw문을 사용해 예외를 던질 수 있어요.
메서드에서 검사 예외를 던질 때는 아래 예시처럼 throws 키워드를 사용해 메서드 시그니처에 이를 명시해야 해요.
그래야 이 메서드를 호출하는 코드가 예외가 던져질 수 있음을 미리 예상하고 그에 맞게 처리하게 돼요.
public class InsufficientBalanceException extends Exception {
}
public class BankAccount {
public void withdraw(double amount) throws InsufficientBalanceException {
if (balance < amount) {
throw new InsufficientBalanceException();
}
// rest of the method implementation
}
}
메서드에서 비검사 예외를 던질 때는 메서드 시그니처에 이를 명시할 필요가 없어요. 물론 명시할 수도 있어요.
public class BankAccount {
public void withdraw(double amount) {
if (amount < 0) {
throw new IllegalArgumentException("Cannot withdraw a negative amount");
}
// rest of the method implementation
}
}
Java에서 예외를 처리할 때는 try, catch, finally 키워드를 사용해요.
try 블록으로 감싸요.try 블록 뒤에는 try 블록에서 던져진 예외를 잡는 하나 이상의 catch 블록이 와요.catch 블록 뒤에는 선택적으로 finally 블록이 올 수 있는데, 이 블록은 예외가 던져졌는지와 상관없이 try 블록 다음에 항상 실행돼요.다음 예시는 이 키워드들이 어떻게 동작하는지 보여줘요:
public class ATM {
public void withdraw(BankAccount bankAccount, double amount) {
try {
System.out.println("Withdrawing " + amount);
bankAccount.withdraw(amount);
System.out.println("Withdrawal succeeded");
} catch (InsufficientBalanceException) {
System.out.println("Withdrawal failed: insufficient balance");
} catch (RuntimeException e) {
System.out.println("Withdrawal failed: " + e.getMessage());
} finally {
System.out.println("Current balance: " + bankAccount.getBalance());
}
}
}
이 예시에서 예외가 던져지지 않으면 다음과 같이 출력돼요:
Withdrawing 10.0
Withdrawal succeeded
Current balance: 5.0
하지만 bankAccount.withdraw(amount) 문이 InsufficientBalanceException을 던지면 다음과 같이 출력돼요:
Withdrawing 10.0
Withdrawal failed: insufficient balance
Current balance: 5.0
또는 bankAccount.withdraw(amount)가 비검사 예외를 던지면 다음과 같이 출력돼요:
Withdrawing -10.0
Withdrawal failed: Cannot withdraw a negative amount
Current balance: 5.0
Java에는 _오류_라는 별도의 범주도 있는데, 이는 애플리케이션 외부에서 비롯된 심각한 문제예요.
오류의 한 예로 OutOfMemoryError가 있는데, 이는 애플리케이션이 시스템에서 사용할 수 있는 것보다 더 많은 메모리를 쓰려 할 때 발생해요.
비검사 예외와 마찬가지로 오류도 컴파일 시점에 검사되지 않아요. 차이점은 오류가 시스템 수준의 문제를 나타내며, 일반적으로 애플리케이션이 아니라 Java 가상 머신이나 환경에서 던져진다는 것이에요. 애플리케이션은 일반적으로 오류를 잡거나 처리하려고 해서는 안 돼요.
Java의 모든 오류는 Error 클래스를 상속해요.
이번 연습 문제에서는 간단한 정수 계산기의 오류 처리를 만들어 봐요. 간단하게 진행할 수 있도록 덧셈, 곱셈, 나눗셈을 계산하는 메서드가 미리 준비되어 있어요.
목표는 제대로 동작하는 계산기를 만드는 거예요. 이 계산기는 인자로 16, 51, +를 받으면 16 + 51 = 67처럼 문자열을 반환해요.
CalculatorConundrum calculator = new CalculatorConundrum();
calculator.calculate(16, 51, "+");
// => returns "16 + 51 = 67"
calculator.calculate(32, 6, "*");
// => returns "32 * 6 = 192"
calculator.calculate(512, 4, "/");
// => returns "512 / 4 = 128"
이번 작업에서 구현할 핵심 메서드는 CalculatorConundrum.calculate() 메서드예요.
이 메서드는 인자 세 개를 받아요.
처음 두 인자 operand1, operand2는 연산을 적용할 정수예요.
세 번째 인자 operation은 문자열 타입이고, 이번 연습 문제에서는 다음 연산들을 구현해야 해요:
+ 문자열을 이용한 덧셈* 문자열을 이용한 곱셈/ 문자열을 이용한 나눗셈CalculatorConundrum.calculate() 메서드를 수정해서 잘못된 연산을 처리해요:
operation 인자가 null이면 Operation cannot be null이라는 메시지와 함께 IllegalArgumentException을 던져야 해요.operation 인자가 ""이면 Operation cannot be empty이라는 메시지와 함께 IllegalArgumentException을 던져야 해요.operation 인자가 +, *, / 이외의 연산이면 Operation '{operation}' does not exist이라는 메시지와 함께 IllegalOperationException을 던져야 해요.calculator.calculate(10, 1, null);
// => throws IllegalArgumentException with message "Operation cannot be null"
calculator.calculate(10, 1, "");
// => throws IllegalArgumentException with message "Operation cannot be empty"
calculator.calculate(10, 1, "-");
// => throws IllegalOperationException with message "Operation '-' does not exist"
Java에서는 0으로 나누려고 하면 ArithmeticException이 발생해요.
CalculatorConundrum.calculate() 메서드를 수정해서 이 예외를 잡은 뒤, Division by zero is not allowed이라는 메시지와 잡은 ArithmeticException을 원인으로 하는 IllegalOperationException을 던져요.
calculator.calculate(512, 0, "/");
// => throws IllegalOperationException with message "Division by zero is not allowed"
Exercism에 가입하고 Java 트랙을 개념 26개연습 문제 158개, 그리고 실제 사람의 멘토링과 함께 배우고 익혀 보세요. 모두 무료예요.