📌 에러 vs 예외
에러 (Error) : 하드웨어의 고장으로 인해 응용프로그램 실행 오류가 발생하는 것
예외 (Exception) : 잘못된 사용 도는 코딩으로 인한 오류
에러는 개발자가 대처 불가
에러, 예외 모두 발생 시 프로그램 곧바로 종료
예외는 예외 처리를 통해 계속 실행 상태를 유지할 수 있음
📌 예외 종류
일반 예외 (Exception) : 컴파일러가 예외 처리 코드 여부를 검사하는 예외 (checked)
실행 예외 (Runtime Exception) : 컴파일러가 예외 처리 코드 여부를 검사하지 않는 예외 (unchecked)
자바는 예외가 발생하면 예외 클래스로부터 객체 생성 => 예외 처리 시 사용
모든 에러와 예외 클래스는 Throwable 상속
예외 클래스는 다시 java.lang.Exception 클래스 상속
💡 런타임 vs 컴파일타임
컴파일타임 (Compiletime)
개발자가 소스코드 작성하고 컴파일 과정을 통해 기계어 코드로 변환되어 실행 가능한 프로그램이 되는 과정런타임 (Runtime)
컴파일 과정을 마친 프로그램이 사용자에 의해 실행되어지고, 이러한 응용프로그램이 동작되어지는 때
compiletime exception
프로그램이 성공적으로 컴파일링 되는 것을 방해하는 문법적 문제(괄호가 안 닫힘 etc)나 잘못된 파일 참조와 같은 문제runtime exception
소스코드가 이미 실행가능한 프로그램으로 컴파일 되었다 할지라도 프로그램 실행 중 예상치 못하게 생길 수 있는 예외
💡 Checked Exception vs Unchecked Exception
Checked Exception
Runtime Exception을 상속하지 않는 예외
반드시 예외를 처리하는 코드를 작성해야함 (해결하지 않으면 컴파일 시 예외 발생)
Java Compiler와 JVM이 확인해서 Exception 호출Unchecked Exception
RuntimeException을 상속한 예외
명시적으로 예외처리를 강제하지 않음
[참고]
https://reference-m1.tistory.com/246
https://spaghetti-code.tistory.com/35
📌 예외 처리 코드
예외가 발생했을 때 프로그램의 갑작스러운 종료를 막고 정상 실행을 유지할 수 있도록 처리하는 코드
try-catch-finally 블록 구성
try : 예외 발생 가능 코드
catch : 예외 발생 시 예외를 처리하는 코드
finally : 항상 실행되는 코드 (생략 가능)
정상 실행 : try > finally
예외 발생 : try > catch > finally
📌 e.getMessage()
예외의 원인을 간단하게 출력
e.getMessage() => 에러내용
📌 e.toString()
예외의 Exception 내용(종류)과 원인 출력
e.toString() => java.lang.Exception: 에러내용
📌 e.printStackTrace()
예외의 발생 근원지를 찾아서 단계별로 예외 출력
e.printStackTrace() => java.lang.Exception: 에러내용 at ExeThrowException.main(ExeThrowException.java:5)
📌 다중 catch
다양한 종류의 예외를 별개로 처리
catch 블록이 여러 개라도 단 하나만 실행 (try에서 예외 하나 발생하면 즉시 catch로 가기 때문)
처리할 예외 클래스가 상속 관계일 경우, 하위 클래스 catch 블록을 먼저 작성
(하위 예외도 상위 예외 타입이기 때문에 상위 클래스 catch블록에서 잡음)
📌 두 개 이상의 예외를 하나로 처리
catch(NullPointerException | NumberFormatException e) { ... }
📌 리소스 (Resource)
데이터를 제공하는 객체 (데이터를 읽고 쓰는 객체) => 데이터 자체가 아닌 객체!
사용하기 위해 open 하고 사용이 끝난 후 다른 프로그램에서도 쓸 수 있도록 close 해야 함
(예) 파일 내용을 읽기 위해 파일을 열고 다 읽은 후 파일 닫기
예외가 발생해도 안전하게 닫아야 함 (닫지 않으면 리소스가 불안정하게 남게 됨)
FileInputStream fis = null;
try {
//FileInputStream: file 텍스트 문서를 제공하는 리소스
fis = new FileInputStream("file.txt");
...
} catch (IOException e) {
...
} finally {
// 예외가 발생해도 닫을 수 있도록 finally에 close
fis.close();
}
📌 try-with-resources
예외 발생 여부와 상관없이 리소스를 자동으로 닫아줌
try 괄호에 리소스 여는 코드 작성
try 블록이 정상적으로 실행 완료, 도중 예외 발생 시 리소스의 close() 메소드 호출
try(FileInputStream fis = new FileInputStream("file.txt")) {
...
} catch (IOException e) {
...
} //finally에서 close 필요 없음
📌 try-with-resources 사용 조건
리소스는 java.lang.AutoClosable 인터페이스를 구현해서 AutoCloseable 인터페이스의 close() 메소드 재정의 필요
리소스는 AutoCloseable의 구현 객체가 되어야 함
public class FilInputStream implements AutoCloseable {
private String fileName;
public FileInputStream(String fileName) {
this.fileName = fileName;
}
public void read() {
System.out.println(fileName + "을 읽습니다.");
}
@Override
public void close() throws Exception {
System.out.println(fileName + "을 닫습니다.")
}
}
📌 복수개의 리소스 사용
try (
FileInputStream fis1 = new FileInputStream("file1.txt");
FileInputStream fis2 = new FileInputStream("file2.txt")
) {
...
} catch (IOException e) {
...
}
📌 (+ 추가) 리소스 파일 읽기
📌 예외 떠넘기기
메소드 내부에서 try-catch로 예외 처리하지 않고 메소드를 호출한 곳으로 예외 떠넘김
throws 키워드 사용
떠넘길 예외 클래스를 쉼표로 구분해서 나열
메소드에서 예외를 처리하지 않고 넘겼기 때문에 메소드를 호출한 곳에서 예외 처리
메소드는 기능에 집중하고, 사용자는 발생할 수 있는 예외를 미리 확인하고 처리 루틴을 설정
우리가 AOP를 이용해 Exception을 따로 처리한 것!
Service의 메소드에서는 기능만을 수행하게 하고 예외를 exception 폴더에서 처리하게 함
리턴타입 메소드명(매개변수, ...) throws 예외클래스1, 예외클래스2, ... { ... }
public void method1() {
try {
method2(); //메소드 호출
} catch (ClassNotFoundException e) {
System.out.println("예외 처리: " + e.getMessage());
}
}
public void method2() throws ClassNotFoundException {
Class.forName("java.lang.String2");
}
📌 (+ 추가) Class.forName
Class 클래스는 클래스들의 정보(필드, 메소드 ...)를 담는 메타 클래스
JVM이 Class를 통해 클래스들에 대한 정보 로드
java.lang.String2 = new java.lang.String2(); 와 같은 동작
하지만, 실제로 변수와 대입도 없음
인스턴스 생성과 초기화가 이루어짐
[출처] https://kyun2.tistory.com/23
📌 나열할 예외 클래스가 많은 경우
리턴타입 메소드명(매개변수, ...) throws Exception { ... }
📌 main()에서 예외 떠넘기기
JVM이 최종적으로 예외 처리
JVM은 예외의 내용을 콘솔에 출력하는 것으로 예외 처리
public static void main(String[] args) throws Exception { ... }
📌 사용자 정의 예외
표준 라이브러리에 존재하지 않은 예외를 직접 예외 클래스를 정의해서 사용
일반 예외 선언 : Exception의 자식 클래스로 선언
실행 예외 선언 : RuntimeException의 자식 클래스로 선언
기본 생성자와 예외 메시지를 입력받는 생성자를 선언
예외 메시지는 부모 생성자 매개값으로 넘겨줌
예외 객체의 공통 메소드인 getMessage()의 리턴값으로 사용하기 위해
public class XxxException extends [Exception | RuntimeException] {
public XxxException() {
}
public XxxException(String message) {
super(message);
}
}
📌 예외 발생 시키기
throw 키워드와 함께 예외 객체 제공
메시지 제공은 생성자 매개값으로 전달
//try-catch
void method() {
try {
...
//Exception에 사용자 정의 예외 클래스가 들어가도 됨
throw new Exception("Exception Message");
...
} catch (Exception e) {
//getMesage로 Exception Message 가져옴
String message = e.getMessage();
}
}
//대부분 throws 키워드로 예외 떠넘김
void method() throws Exception {
...
throw new Exception("Exception Message");
...
}
📌 Throw vs Throws
throw는 예외를 직접 생성해 일부러 던져줌
throws는 throw로 만든 예외를 밖으로 던짐 (예외처리의 주체를 바꿔줌)
📌 예외 복구
예외가 발생하면 다른 작업 흐름으로 유도
try-catch-finally
📌 [예시] - 네트워크가 환경이 좋지 않아 서버에 접속이 안 되는 상황
int maxtry = MAX_RETRY;
while(maxretry --> 0) {
try {
//예외 발생 가능성 있는 시도
return; //작업 성공시 리턴
} catch (SomeException e) {
//로그 출력, 정해진 시간만큼 대기
} finally {
//리소스 반납 및 정리 작업
}
}
throw new RetryFailedException(); //최대 재시도 횟수를 넘기면 직접 예외 발생
예외가 발생하면 그 예외를 잡아서 일정 시간만큼 대기하고 다시 재시도를 반복
최대 재시도 횟수를 넘기면 예외 발생
재시도를 통해 정상 흐름을 타게 하거나, 예외가 발생하면 이를 미리 예측해서 다른 흐름으로 유도
예외가 발생해도 정상적으로 작업 종료 가능
📌 예외처리 회피
예외 복구와 처리를 하지 않고 호출한 쪽으로 던짐
throws
무책임하게 던지는 것은 위험
호출한 쪽에서 다시 예외를 받아 처리하도록 하거나, 해당 메소드에서 이 예외를 던지는 것이 최선의 방법이라는 확신이 있을 때만 사용
📌 예외 전환
호출한 쪽으로 던질 때 명확한 의미를 전달하기 위해 다른 예외로 전환하여 던짐
어떤 예외인지 분명해야 처리가 수월해짐
catch (SQLException e) {
...
throw DuplicateUserIdException();
}