[WhiteShip][9주차] 예외처리
WhiteShip Java Live Study 의 커리큘럼을 따라 개인적으로 공부한 내용입니다.
Codestates BE 42 : Study, Commercise 01 Java
Thanks to
" Codestates_SEB_BE_42 : Commercise "
강지은
22.11.11
어제 TIL 예외처리 부분이 날라가서
혼자 ... 아침 6시부터... 정리했다... ㅠㅠㅠㅠㅠㅠ
화이트십 목차가 존재하지만
제가 읽기 쉬운 순서로 바꿨습니다!
9주차 : 예외처리
목차
- 예외처리
- 에러발생원인
- 컴파일에러, 런타임에러
- Exception과 Error의 차이는?
- 자바가 제공하는 예외 계층 구조
- throwable 클래스
- RuntimeException과 RE가 아닌 것의 차이
- 자바에서 예외 처리 방법
- 예외복구, 예외처리 회피, 예외전환
- 예외처리시 주의할점
- try, catch
- 다중 catch문과 주의할점
- throw, throws, finally
- 커스텀한 예외 만드는 방법
✅ 예외처리
에러에 대응할 수 있는 코드를 사전에 미리 작성해
비정상적인 종료를 막고, 정상적인 실행 상태를 유지하기 위함.
⏺ 에러가 발생하는 원인
에러의 원인은 정말 수없이 다양하다.
- 사용자의 입력 오류
- 네트워크 연결 끊김
- 디스크 메모리 공간 부족 등 물리적 한계
- 개발자의 코드 에러
- 존재하지(유효하지) 않는 파일 불러오기
⏺ 컴파일 에러, 런타임 에러
◼ 컴파일에러
컴파일 할 때 발생하는 에러
Syntax Error : 문법적인 문제 (세미콜론생략, 오탈자, 잘못된 자료형, 잘못된 포맷)
자바 컴파일러가 오류를 감지해 사용자에게 친절하게 알려줌 (빨간줄)
java: ')' expected
◼ 런타임에러
런타임시에 발생하는 에러
개발자가 컴퓨터에 수행할 수 없는 특정한 요청을 작업할 때 발생함.
System.out.println(4 / 0); // 예외 발생
Exception in thread "main" java.lang.ArithmeticException: / by zero
at RuntimeErrorTest.main(RuntimeErrorTest.java:5)
ArithmeticException : 숫자 0으로 나눴을 때 발생하는 에러
✅ Exception과 Error의 차이는?
에러(Error) : 한번 발생하면, 복구하기 어려운 수준의 심각한 오류 (ex - StackOverflowError, OutOfMemoryError)
예외(Exception) : 잘못된 사용, 코딩으로인해 상대적으로 미약한 수준의 오류 (코드수정을 통해 수습이 가능)
✅ 자바가 제공하는 예외 계층 구조
✔ Throwable 클래스
모든 예외와 에러 클래스들의 조상이 되는 클래스 (Object의 자식)
예외나 에러에 대한 정보를 확인할 수 있는 메소드를 가지고 있음
- getMessage() : 해당 throwable 객체에 대한 자세한 내용을 반환
- printStackTrace() : 예외나 에러가 발생 할 때까지의 이력을 출력
- toString() : 해당 throwable 객체의 간단한 내용을 반환
Throwable 밑에 Error와 Exception이 있는데,
에러는 시스템 레벨의 심각한 에러, Exception은 개발자가 로직을 추가해 처리할 수 있다.
✅ RuntimeException과 RE가 아닌 것의 차이
RuntimeException을 상속하지 않고 꼭 처리해야 하는 Checked Exception
명시적으로 처리하지 않아도 되는 Unchecked Exception
⏺ RuntimeException (UncheckedException)
런타임 시에 예외를 발생
실행 전에는 컴파일 에러를 발생시키지 않는다.
예외처리를 강제하지는 않지만 해당 내용이 런타임 시에 예외를 발생시킬 수 있음을 인지하면 예외를 처리해주는 것이 좋음
→ NullPointerException, IndexOutOfBoundsException, ArithmeticException 등의 로직을 짜면서 발생가능한 exception
💻 ArithmeticException
0으로 정수를 나누는 경우에 발생, 컴파일 에러는 발생 x
package com.livestudy.ninth;
public class RuntimeExceptionTest {
public static void main(String[] args) throws IOException {
System.out.println(3/0); //컴파일 에러 발생 X
}
}
런타임시 에러 발생 확인
즉 ArithmeticException은 UncheckedException이다.
- 트랜잭션을 Rollback한다.
- 트랜잭션 적용시 전파방식과 롤백 규칙등을 적절히 사용해 효율적인 애플리케이션을 구현할 수 있다.
- 나중에 볼만한 자료 https://cheese10yun.github.io/checked-exception/
⏺ 그 외
RuntimeException이 아닌 Exception (CheckedException)
- 컴파일 단계에서 확인 가능함. → 실행하기 전에 예외를 반드시 처리해야함.
- 예외가 발생하면 Roll-back 하지 않고 예외를 던져준다.
→ IOException, SQLException
💻 BufferedReader
데이터를 모두 읽은 이후에는 해당 객체를 종료해줘야 하는데, close() 하면, 컴파일 에러가 남.
→ IOException이 CheckedException이기 때문
내용 그대로 Stream으로 뭘 써야 하는데 이게 닫혀 버렸기 때문에 발생함.
package com.livestudy.ninth;
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class RuntimeExceptionTest {
public static void main(String[] args) {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
br.close(); //컴파일 에러 발생 (IOException)
}
}
✅ 자바에서 예외 처리 방법 (try, catch, throw, throws, finally)
- 예외복구: 예외 발생시 다른 작업 흐름을 유도함.
- 예외처리 회피 : 예외 복구, 처리하지 않고 호출한 쪽으로 던져버림
- 예외 전환 : 호출한 쪽으로 던질 때 명확한 의미를 전달하기 위해, 다른 예외로 전환해 던짐
⏺ 예외복구
예외가 발생해도 Application은 정상적인 흐름으로 진행됨
→ 재시도를 통해 예외를 복구함.
→ 네트워크 환경이 좋지 않아 서버에 접속이 안되는 상황에 적용하면 효율적
- 예외 발생시 예외를 잡아서 일정시간 대기, 다시 재시도함
- 최대 예외 횟수를 넘기면 예외 발생 시킴.
- 재시도를 통해 정상적 흐름을 타거나, 다른 흐름을 유도
int maxretry = MAX_RETRY;
while(maxretry -- > 0) {
try {
// 예외가 발생할 가능성이 있는 시도
return; // 작업성공시 리턴
}
catch (SomeException e) {
// 로그 출력. 정해진 시간만큼 대기
}
finally {
// 리소스 반납 및 정리 작업
}
}
throw new RetryFailedException(); // 최대 재시도 횟수를 넘기면 직접 예외 발생
⏺ 예외처리 회피
예외가 생기면 throws를 통해 호출한 쪽으로 예외를 던지고, 처리를 회피하는 것
무책임하게 던지는것은 위험하다
→ 호출한 쪽에서 받아 처리하도록 하거나
→ 해당 메소드에서 예외를 던지는게 최선의 방법이라는 확신이 있을 때 사용
public void add() throws SQLException {
... // 구현 로직
}
⏺ 예외 전환
예외를 잡아서 다른 예외를 던짐
호출한 쪽에서 예외를 받아 처리할 때 좀더 명확한 인지 가능
→ CheckedException중 복구 불가능한 예외가 잡히면,
Unchecked Exception으로 전환해 다른 계층에서 일이이 예외를 선언할 필요가 없도록 할수도 있음.
catch(SQLException e) {
...
throw DuplicateUserIdException();
}
⏺ 예외처리시 주의할 점
💥 예외를 잡아놓고 아무런 처리도 하지 않는것은 정말 위험한 행위이다.
💥 try-catch로 잡아두고 catch를 비워두면 컴파일 오류는 나지 않겠지만,
예외발생시 원인 파악이 어려워 개발은 물론 유지보수에 굉장한 민폐가 된다고 한다.
- 어떤처리를 해야하는지 모르더라도 무작정 catch하고 무시하거나, throw 해버리면 안된다!
◼ try
코드가 실행되는 부분으로, 예외를 잡아내기 위한 부분
예외가 발생한다면, catch 블록으로
예외가 발생하지 않는다면 catch 블록을 실행하지 않고 finally 블록으로 이동
try {
// 코드 (예외 발생 코드 경우 -> catch 블록)
// 코드 (예외 발생 코드 아닌 경우 -> try 블록 끝난 후 finally 블록)
}
◼ catch
try 블록에서 예외가 발생시 발생한 코드의 다음 부분은 실행하지 않고
바로 catch 블록으로 넘어와
catch 블록의 예외타입이 catch 블록을 실행함.
catch (예외타입 변수명) {
// 예외 발생시 실행하는 코드 블록
}
✔️ 다중 catch문
catch 블록 여러개 사용 가능
try {
// 코드
} catch (예외타입 변수명1) {
// 1과 관련된 예외 시 처리 구문
} catch (예외타입 변수명2) {
// 2와 관련된 예외 시 처리 구문
}
여러 개의 catch 문으로 구성된 형식을 하나의 catch 블록으로 묶는 것도 가능
try {
// 코드
} catch (예외타입 | 예외타입 변수명) {
// 코드
}
📌 다중 catch 문 주의할 점
반드시 앞의 예외 객체 타입이 뒤의 예외 객체 타입 보다 작아야한다.
즉, 앞에 나오는 예외 객체 타입이 뒤에 나오는 예외 객체 타입의 서브 클래스인 경우에 가능.
앞에 나오는 예외 객체 타입이 상위 클래스라면 뒤의 예외를 하나의 예외 클래스로 처리할 수 있기 때문에 컴파일 에러를 발생한다다.
아래 예제는 Exception가 위에 IOException이 아래에 있는데, 특정 IOException이 났음에도 Exception에서 잡힐 수도 있는 문제가 생긴다 → 컴파일러가 에러를 발생시킨다.
//// 컴파일 에러
try {
// 코드
} catch (Exception e) {
} catch (IOException e) {
}
//// 컴파일 에러
try {
// 코드
} catch (Exeption | IOException e) {
// 코드
}
◼ finally
try-catch 구문에서 예외가 발생 유무에 상관없이 항상 해당 블록을 실행
try-catch구문에서 필수는 아님
finally {
// 반드시 실행하는 코드
}
◼ throw
인위적으로 예외를 발생시킬 때 사용하는 키워드
프로그램 동작 중에 개발자가 원하는 조건을 만족하지 않을 때 더 이상 코드가 진행하지 못하게 예외를 발생시킬 때 사용함.
대표적으로 라이브러리를 만들 때 사용
💻 N까지 양수의 합을 구하는 메소드
N의 유효성 체크 후에 (parameter N은 음수가 들어오면 안되기 때문에) 예외를 발생시켜서 넘겨준다.
// N까지의 양수의 합
private static int sumPositiveNum(int N) throws Exception {
if( N < 0)
throw new Exception("N은 양의 정수이어야 합니다.");
int sum = 0;
for(int i =1;i<=N;i++){
sum += i;
}
return sum;
}
만약 sumPositiveNum() 메소드의 파라미터로 음수를 넣어준다면 위의 코드에서 정의한 것과 동일하게 아래의 사진처럼 예외를 발생시킬 것이다.
◼ throws
throws 키워드는 예외가 발생하는 경우 예외를 발생시킨 메소드의 호출 지점으로 예외를 던져 처리한다.
해당 예제에서는 BufferedReader 객체를 종료할 때 close()메소드에서 예외가 발생함.
이 때, try-catch 블록으로 처리할 수도 있지만, 해당 메소드를 호출한 main메소드에 예외를 처리해달라는 의무를 전가해줄 때 throws를 사함.
package com.livestudy.ninth;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class ExceptionTest {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
br.close();
}
}
◼ try-with-resources (7이상)
try 블록에서 자원 객체를 전달하면 try 블록이 끝난 이후 자동으로 자원을 종료(해제)해주는 역할을 한다.
따라서, finally와 catch 블록에서 종료를 해줄 필요가 없다.
try-with-resources는 AutoClosable 인터페이스의 close() 메소드를 구현해야한다.
public class ExceptionTest implements AutoCloseable{
private static String readLine() throws IOException {
// try-with-resources
try (BufferedReader br = new BufferedReader(new InputStreamReader(System.in))){
return br.readLine();
}
}
@Override
public void close() throws Exception {
throw new IOException();
}
}
✅ 커스텀한 예외 만드는 방법
Exception 클래스를 상속받아 사용자가 직접 예외를 커스텀해서 사용할 수 있다.
- 일반적으로 Exception 클래스를 상속 (필요에 따라 예외클래스 선택)
- 생성자를 통해 어떤 값이 들어왔을 때 필드-에러코드를 설정하는 식
- public 으로 에러코드값을 리턴하도록 설정해줬다.
💻 CustonException
- message만 파라미터로 들어오면, ERR_CODE 는 200
- int ERR_CODE까지 파라미터로 들어오면 ERR_CODE 는 100
- 커스텀 예외 클래스의 생성자를 확인하면, super(message)라는 메소드를 확인할 수 있음.
- 이는 위에서 Throwable 클래스의 getMessage()를 통해 메세지를 처리함
public class CustomException extends Exception {
private final int ERR_CODE;
//에러 메세지(커스텀)
public CustomException(String message) {
this(message, 200);
}
// 에러 메세지(커스텀), 에러 코드(커스텀)
public CustomException(String message, int ERR_CODE) {
super(message);
this.ERR_CODE = ERR_CODE;
}
// 에러코드 리턴
public int getErrorCode() {
return ERR_CODE;
}
}
💻 Main
- main(), checkAge()
- main에서 try-catch를 통해 어떤 사람이 태어난 년도를 확인한다.
- checkAge에서 유효성 검증을 해주는 코드를 작성, 유효범위에 어긋나면
- throw : 인위적으로 예외를 발생시킨다 — 여기에서 CustomException을 new연산자로 CustomException을 생성하고, 인자값을 전달한다.
- throws 키워드를 사용했기 때문에 해당 메소드의 호출 지점으로 예외가 던져진다.
- main()으로 던져진 CustomException은 catch 가 잡아서, e로 사용할 수 있다.
- CustomException은 Throwable을 상속받았기 때문에 Throwable 메서드를사용할수있다.
- getMessage(), printStackTrace(), toString()
public class Main {
public static void main(String[] args){
try{
checkAge(1300);
//checkAge(2022);
}catch (CustomException e){
e.printStackTrace(); //예외/에러 발생할 때까지의 이력을 출력
System.out.println("ERR_MSG : "+ e.getMessage()); // 자세한 내용 반환
System.out.println("ERR_CODE : "+ e.getErrorCode());
}
}
private static void checkAge(int birthYear) throws CustomException{
final int YEAR = 2021;
if (YEAR - birthYear < 0 ){
throw new CustomException("Too big birth year");
}
if(birthYear < 1800){
throw new CustomException("maybe wrong birth year", 100);
}
}
}
References
- 예외처리
- 에러발생원인
- 컴파일에러, 런타임에러
- Exception과 Error의 차이는?
- 자바가 제공하는 예외 계층 구조
- throwable 클래스
- RuntimeException과 RE가 아닌 것의 차이
- 자바에서 예외 처리 방법
- 예외복구, 예외처리 회피, 예외전환
- 예외처리시 주의할점
- try, catch
- 다중 catch문과 주의할점
- throw, throws, finally
- 커스텀한 예외 만드는 방법