목표
- 예외와 오류를 이해한다.
- 자바에서 예외 처리 방법을 이해한다.
예외와 오류, 예외 처리
예외와 오류의 개념과 차이
예외
- 예외(Exception)은 프로그램의 실행 중에 예기치 않은 상황이 발생하여 정상적인 흐름을 방해하는 상황
- 예외는 일반적으로 잘못된 입력, 파일을 찾을 수 없음, 네트워크 연결 오류 등과 같은 상황에서 발생
- 가장 대표적인 예시는 파일 입출력에 관련된
IOException
과 객체를 찾을 수 없는 NullPointerException
- 예외는 예외 클래스를 통해 구성, Java에서는 Checked 예외와 Unchecked 예외로 구분
- Checked 예외는 컴파일 시 체크되는 Compile Exception
- Unchecked 예외는실행 시에 예외가 발생하는 Runtime Exception
오류
- 오류(Error)는 프로그램이 실행 중에 심각한 시스템 수준의 문제로 인해 예기치 않은 동작이 발생하는 상황
- 메모리 부족, 스택 오버플로우, JVM 내부 오류 등과 같은 심각한 문제로 인해 발생
- 개발자가 처리할 수 없는 문제로 실행 중이던 프로그램이 중단되고 종료 됨
예외 처리의 중요성
- 예외처리의 가장 큰 목적은 예외 상황을 감지하고 적절한 조치를 취함으로써 프로그램이 예기치 않은 상황에서 비정상적인 동작을 방지하는 것
- 개발자가 인식할 수 있는 상황에서 예외 상황에 대한 정보를 기록하고 추적하고, 디버깅과 로깅을 통해 프로그램을 관리하기 위한 것
- 또한, 사용자 예외를 통해 사용자에게 적절한 오류 메세지를 전달할 수 있음
자바 예외 클래스
- 자바는 기본적으로 Throwable 클래스를 상속 받아 예외 클래스를 처리함
- Exception과 Error 클래스로 분리
Checked 예외와 Unchecked 예외
Checked 예외
- RuntimeException 클래스를 상속받지 않은 예외
- 컴파일러에 의해 체크되어야 하는 예외로, 예외 처리를 강제화하여 반드시 예외 처리를 해주어야 함
- 이를 위해 try-catch 문이나 throws 문을 사용하여 예외 처리할 수 있으며, IDE를 사용하는 경우 예외를 사용해야 한다는 어시스트를 제공
Unchecked 예외
- RuntimeException 클래스를 상속받은 예외
- 컴파일러가 체크하지 않으며, 실행 시에 예외가 발생
- 예외 처리를 강제화하지 않으므로, 개발자의 책임 아래 예외 처리가 이루어짐
- 대표적으로
NullPointerException
, ArrayIndexOutOfBoundsException
등이 있으며 개발자가 조건을 통해 런타임 예외를 방지하도록 처리함
Exception과 Error 클래스의 관계
- Exception 클래스는 Checked 예외와 Unchecked 예외를 모두 포함하는 기본 클래스
- Error 클래스는 Exception 클래스의 하위 클래스로 프로그램이 복구 불가능한 상태에 이르게 하고 프로그램을 종료시키는 역할
- 일반적으로 개발자는 Exception 클래스와 그 하위 클래스인 Checked 예외와 Unchecked 예외를 통해 예외를 처리함
예외 처리 방법
try-catch 문을 사용한 예외 처리
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class ExceptionHandlingExample {
public static void main(String[] args) {
// try-catch 문을 사용한 예외 처리
try {
int result = divide(10, 0);
System.out.println("나눈 결과: " + result);
} catch (ArithmeticException e) {
System.out.println("0으로 나눌 수 없습니다.");
}
}
// 0 나누기로 ArithmeticException 발생
private static int divide(int num1, int num2) {
return num1 / num2;
}
}
여러 개의 catch 블록 사용하기
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class ExceptionHandlingExample {
public static void main(String[] args) {
// 여러 개의 catch 블록 사용하기
try {
String str = null;
System.out.println("문자열 길이: " + str.length());
} catch (NullPointerException e) {
System.out.println("Null 객체에 접근하였습니다.");
} catch (Exception e) {
System.out.println("예외가 발생했습니다.");
}
}
}
finally 블록의 역할과 활용 방법
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class ExceptionHandlingExample {
public static void main(String[] args) {
// finally 블록의 역할과 활용 방법
try {
int result = divide(10, 2);
System.out.println("나눈 결과: " + result);
} catch (ArithmeticException e) {
System.out.println("0으로 나눌 수 없습니다.");
} finally {
System.out.println("finally 블록 실행");
}
}
// 0 나누기로 ArithmeticException 발생
private static int divide(int num1, int num2) {
return num1 / num2;
}
}
try-with-resources 문을 사용한 자원 관리
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class ExceptionHandlingExample {
public static void main(String[] args) {
// try-with-resources 문을 사용한 자원 관리
try (BufferedReader reader = new BufferedReader(new FileReader("file.txt"))) {
String line = reader.readLine();
System.out.println("파일 내용: " + line);
} catch (IOException e) {
System.out.println("파일을 읽을 수 없습니다.");
}
}
}
예외 발생과 전파
메소드에서 예외 발생시키기 (throw)
- 개발자가 예외를 인식하고 처리하기 위해 직접 예외를 발생 시키는 경우
throw
키워드를 사용하여 예외 객체를 생성하고, 해당 예외를 호출한 곳으로 전달
호출자에게 예외 전파하기 (throws)
- 실행한 메소드가 예외를 발생시키지 않고, 예외 처리를 메소드를 호출한 곳으로 위임하고 싶은 경우 throws 키워드를 사용하여 예외를 호출자에게 전파할 수 있음
- 메소드 내에서 발생한 예외를 호출한 곳에서 처리할 수 있으며, 메소드 선언부에
throws
키워드를 사용하여 해당 메소드에서 발생할 수 있는 예외를 명시
예외 전파의 규칙
- checked 예외는 RuntimeException 클래스를 상속받지 않은 예외들로, throws 키워드를 사용하여 예외를 선언하거나 반드시 try-catch 블록 내에서 처리
- unchecked 예외는 RuntimeException 클래스를 상속받은 예외들로, throws 키워드로 선언하지 않아도 예외처리를 강제하지 않음
예제
public class ExceptionPropagationExample {
public static void main(String[] args) {
// Unchecked Exception인 NullPointerException
try {
String str = null;
int length = str.length(); // NullPointerException 발생
System.out.println("문자열 길이: " + length);
} catch (NullPointerException e) {
System.out.println("NullPointerException이 발생했습니다.");
}
try {
calculateDivision(10, 0);
} catch (ArithmeticException e) {
System.out.println("예외가 발생했습니다: " + e.getMessage());
}
try {
readFile("invalidFile.txt");
} catch (IOException e) {
System.out.println("예외가 발생했습니다: " + e.getMessage());
}
}
// Cheked Exception 메소드에서 예외 발생시키기 (throw)
private static void calculateDivision(int num1, int num2) {
if (num2 == 0) {
throw new ArithmeticException("0으로 나눌 수 없습니다.");
}
int result = num1 / num2;
System.out.println("나눈 결과: " + result);
}
// Cheked Exception 호출자에게 예외 전파하기 (throws)
private static void readFile(String fileName) throws IOException {
FileReader fileReader = new FileReader(fileName);
BufferedReader reader = new BufferedReader(fileReader);
String line = reader.readLine();
System.out.println("파일 내용: " + line);
reader.close();
}
}
예외 처리의 디자인 패턴
일반적인 경우
public class FileProcessor {
public void processFile(String filePath) {
try {
// 파일을 읽어오는 작업 수행
FileReader fileReader = new FileReader(filePath);
// 파일 내용 처리 작업 수행
// ...
} catch (FileNotFoundException e) {
// 파일을 찾을 수 없는 경우 처리
System.out.println("파일을 찾을 수 없습니다: " + filePath);
} catch (IOException e) {
// 파일 처리 중 IO 오류가 발생한 경우 처리
System.out.println("파일 처리 중 오류가 발생했습니다: " + e.getMessage());
} finally {
// 사용한 리소스 정리
closeResources();
}
}
private void closeResources() {
// 사용한 리소스 정리
// ...
}
}
- 예외 처리의 종류를 세분화하고, 여러 개의
catch
블럭을 사용하여 가독성을 향상 시킴
- finally 구문을 사용하여 IO 작업에 대한 리소스를 처리
복잡한 예외 처리의 경우 - Exception Chain
try {
// 예외가 발생할 수 있는 작업 수행
} catch (Exception e) {
// 예외를 전파하여 상위 호출자에게 처리를 맡김
throw e;
}
- 예외 처리를 중간에서 처리하지 않고, 호출자에게 모두 넘겨 한곳에서만 예외를 처리하도록 구성
예외 처리 메소드 추출
try {
// 예외가 발생할 수 있는 작업 수행
} catch (Exception e) {
handleException(e);
}
// 예외 처리 메서드
private void handleException(Exception e) {
// 예외 처리 로직 작성
}
- 커스텀 예외로 반복되는 예외 처리와 예외 처리 로직에 대한 별도 메소드를 추출
예외 로깅
try {
// 예외가 발생할 수 있는 작업 수행
} catch (FileNotFoundException e) {
logger.error("파일을 찾을 수 없습니다", e);
} catch (IOException e) {
logger.error("IO 오류가 발생했습니다", e);
} catch (Exception e) {
logger.error("예외가 발생했습니다", e);
}
- 예외 발생에 대한 디버깅을 위해 로그를 통해 예외 정보를 기록