프로그램 코드에 의해 수습될 수 없는 심각한 오류
java.lang.Error 클래스의 하위 클래스들
시스템이 비정상적인 상황인 경우에 사용(메모리가 부족하는 등...)
주로 JVM 에서 발생시키기 때문에, 애플리케이션 코드에서 잡아서는 X
잡아서 대응할 수 있는 방법도 X
→ 따라서 시스템 레벨에서 특별한 작업을 하는게 아니라면, 이러한 에러 처리는 하지 않아도 무방
프로그램 코드에 의해 수습될 수 있는 다소 미약한 오류
Error와 달리, java.lang.Exception 클래스 + 하위 클래스들은 어플리케이션 코드에서 예외가 발생하였을 경우에 사용
Exception 은 체크 예외 / 언체크 예외 로 구분된다.
(상속계층도 中 에러와 예외계층도)
산술적인 연산에 오류 있을 시 (어떤 수를 0으로 나누는 경우 등...)
객체 배열에 잘못된 객체 유형을 저장할 경우
추상 클래스 Animal 의 상속을 받은 Dog 클래스, Cat 클래스가 있다.
public class ClassCastExceptionExample {
public static void main(String[] args) {
Dog dog = new Dog();
changeDog(dog); //Dog 객체의 매개값으로 dog 를 줌
Cat cat = new cat();
changeDog(cat); // Cat 객체의 매개값으로 cat 을 줌
public static void changeDog(Animal animal) {
if (animal instanceof Dog) {
Dog dog = (Dog) animal; // 이미 animal은 Cat 객체의 매개값으로 cat 을 줬기 때문에, Dog 타입으로 변할 수 없게 되어서ClassCastException 발생
}
}
}
}
클래스 간의 형 변환 오류시 발생
instanceof : 객체의 타입을 확인할 때 사용
매개변수가 의도치 않는 상황 유발시
(메소드의 전달 인자값이 잘못될 경우)
public class NumberFormatExceptionExample {
public static void main(String[] args) {
String data1 = "100";
String data2 = "a100"; // data2 는 문자
int value1 = Integer.parseInt(data1); // "100"인 문자 data1 은 숫자로 변환 가능
int value2 = Integer.parseInt(data2); // "a100"인 문자 data2 은 숫자로 변환 불가능 → NumberFormatException 발생
int result = value1 + value2;
System.out.println(data1 + "+" + data2 + "=" + result);
}
}
숫자형 인자를 요구하는 매개변수에 숫자형이 아닌 인자(문자)가 주어진 경우
("5" → 5, "a" → X)
인덱스 매개변수 값이 범위(배열, 문자열, 벡터 등...에서 범위 밖의 index)를 벗어났을 때
배열에서 인덱스 범위를 초과할 경우 발생
(길이가 3인 배열에서는 arr[0] ~ arr[2]를 사용할 수 있으나, 실제론 arr[3]을 사용할 경우)
잘못된 코드
public class ArrayIndexOutOfBoundsExceptionExample {
public static void main(String[] args) {
String data1 = args[0]; // ArrayIndexOutOfBoundsException 발생 → 실행 매개값이 없으므로, 인덱스 사용이 불가능
String data2 = args[1];
System.out.println("args[0]: " + data1);
System.out.println("args[1]: " + data2);
}
}
올바른 코드
public class ArrayIndexOutOfBoundsExceptionExample {
public static void main(String[] args) {
if(args.length == 2) { // 배열값을 일기 전에 배열의 길이를 먼저 조사한다
String data1 = args[0];
String data2 = args[1];
System.out.println("args[0]: " + data1);
System.out.println("args[1]: " + data2);
} else {
System.out.println("두 개의 실행 매개값이 필요");
}
}
객체가 메소드 호출하기 위한 상태가 아닐 시
예시 1
public class NullPointExceptionExample {
public static void main(String[] args) {
String data = null; // 변수 data 는 null값을 가지므로, String 객체를 참조하지 않는다.
System.out.println(data.toStirng()); // 그러나, String 객체의 toString() 메소드를 호출하고 있다.
}
}
예시 2
// 수정 전
List<String> names = getNames();
names.sort(); // names가 null이라면, NPE가 발생
// 수정 후
List<String> names = getNames();
if (names != null) { // NPE를 방지하기 위해서는 null 검사를 해야 함
names.sort();
}
null을 허용하지 않는 메서드에 null을 건넸을 때, 매개변수 값이 null 일 때
(Null 객체 참조 시)
null 검사를 해야하는 변수 多 경우, 코드가 복잡해지고 번거로우므로
null 대신 초기값을 사용하길 권장하기도 함
/******* NPE 발생 예시 *******/
public static void main(String[] args) {
String a = null;
System.out.println("1번째============");
if (a == "god") {
System.out.println("참");
} else {
System.out.println("거짓"); //거짓 출력
}
System.out.println("2번째============");
if (a.equals("god")) { // NPE 발생!
System.out.println("equals => 참");
} else {
System.out.println("equals => 거짓");
}
}
/******* 결과 *******/
1번째============Exception in thread "main"
거짓
2번째============
java.lang.NullPointerException
의미 없는 null 을 Parameter 로 넘기지 않기
int size = param.getSize();
totalPages = (int) (totalCnt / size);
size 파라미터(인자)가 다음 코드로 넘어가는데
이 경우 null 이 넘어가게 되면, int 타입이므로 default 값인 0 을 선언하게 된다.
이 떄, '숫자 / 0' 이 되면서 ArithmeticException 가 발생하게 된다.
"문자열"부터 제시해서, 이를 비교의 주체로 삼기
/******* 예시 1 : 기본 *******/
String a = null;
System.out.println(a.indexOf("갓"));
/******* 예시 2 : equals 사용 *******/
public static void main(String[] args) {
String a = new String("god");
System.out.println("1번째============");
if (a == "god") {
System.out.println("참");
} else {
System.out.println("거짓"); // 거짓출력
}
System.out.println("2번째============");
if (a.equals("god")) {
System.out.println("equals => 참"); // 참출력
} else {
System.out.println("equals => 거짓");
}
}
/******* 결과 1 *******/
Exception in thread "main" java.lang.NullPointerException
String a = null;
if(a != null){
System.out.println(a.indexOf("갓"));
}
/******* 결과 2 *******/
1번째============
거짓
2번째============
equals => 참
toString() 대신, valueOf() 사용
toString() 를 사용할 경우
public static void main(String[] args) {
Integer a = 1;
System.out.println(a.toString());
a = null;
System.out.println(a.toString());
}
// 결과
1
a 변수에 null 이 올 경우, NullPointerException 이 발생하게 된다.
valueOf() 를 사용할 경우
public static void main(String[] args) {
Integer a = null;
System.out.println(String.valueOf(a));
}
// 결과
null
Chaining 메소드 호출 자제하기
// Method Chaining 기본 문법 형태
객체.메소드().메소드().메소드();
// 예시
String polcValCont = moStorePolicyService.getStorePolcBase(polcCd).getPolcValCont();
이런 체이닝 메소드에서 "NPE"가 발생하면, 디버깅 하기가 어렵다.
Apache Commons에서 제공하는 StringUtils 사용하기
public static void main(String[] args) {
System.out.println(StringUtils.isEmpty(null)); // true
System.out.println(StringUtils.equals("1", null)); // false
System.out.println(StringUtils.equals(null, "1")); // false
System.out.println(StringUtils.indexOf("갓", null)); // -1
System.out.println(StringUtils.indexOf(null, "갓")); // -1
System.out.println(StringUtils.upperCase(null)); // null
}
// 결과
true
false
false
-1
-1
null
Spring 을 사용하고 있다면, @NotNull 사용하기
보안 위반 발생 시, 보안 관리 프로그램에서 발생
객체가 메소드를 지원하지 않는 경우
예외 상황을 파악하고 문제를 해결해서 정상 상태로 돌려놓는 것
만약 예외로 어떤 작업의 처리가 불가능하다면, 다르게 작업을 처리하도록 유도
예시 : 다른 API 호출에 실패하였을 경우, 3회 정도 retry 하여 복구되도록 함
// ResultSet 이나 PreparedStatement 와 같은 경우, 메서드에 SQLException 을 던져 호출하는 쪽에서 처리하게끔 정의
public interface ResultSet extends Wrapper, AutoCloseable {
...
boolean next() throws SQLException;
void close() throws SQLException;
...
}
예외 처리를 직접 처리 X, 자신을 호출한 곳으로 던져버리는 것
해당 예외를 처리하는 것이 자신이 해야될 일이 아니라고 느껴진다면, 다른 메소드에서 처리하도록 넘겨줄 때 사용
(단, 무작정 예외를 넘겨주는 것은 무책임한 회피가 될 수 있으므로 상황에 따라 적절히 사용)
방법 1 : throws 문으로 선언해서 예외가 발생하면, 알아서 던져지게 하기
방법 2 : catch 문으로 일단 예외를 잡은 후, 로그를 남기고 다시 예외를 던지기(rethrow)
예외 회피와 마찬가지로, 예외를 복구할 수 없는 상황에 사용
예외 처리 회피와 다르게, 적절한 예외로 변환하여 던짐
목적
언제 예외를 변경하는가?
1. 내부에서 발생한 예외를 그대로 던지는 것이 적절한 의미를 부여하지 못한다해결법
의미있고 추상화된 예외로 바꾼다.예시
새로운 사용자를 등록하고자 할 때 동일한 아이디가 존재하면 SQLException이 발생
그러나, SQLException 에러를 그대로 던지면, 이를 이용하는 서비스 계층에서는 예외 발생 이유에 대해 파악이 어려움
→ DuplicatedUserIdException 과 같은 예외로 바꿔서 던지면,
확실히 의미 전달이 가능하며 상황에 따라 복구작업을 시도할 수도 있을 것이다.
- 체크 예외에 의해, 불필요한 에러 처리가 多 경우
해결법
런타임 예외로 포장하여(언체크 예외로 변경하여), 불필요한 처리 줄이기
→ 복구하지 못할 예외라면, 불필요하게 체크를 할 필요가 없기 때문예시
어플리케이션 로직 상에서 런타임 예외로 포장하여 던지고,
자세한 로그를 남기거나 알림을 주는 등...의 방식으로 처리할 수 있다.
참고: [Spring] 스프링의 다양한 예외 처리 방법(ExceptionHandler, ControllerAdvice 등) 완벽하게 이해하기 - (1/2)
참고: [Spring] @RestControllerAdvice를 이용한 Spring 예외 처리 방법 - (2/2)
참고: [JSP/Spring]WAS(톰켓)와 웹서버(아파치)의 차이
참고: [Java] 체크 예외(Check Exception)와 언체크 예외/런타임 예외 (Uncheck Exception, Runtime Exception)의 차이와 올바른 예외 처리 방법
참고: Spring Exception, 제대로 처리하기
참고: @RestControllerAdvice 를 이용해서 예외 처리하기
참고: [스프링부트] 예외 처리하기(@ExceptionHandler , @RestControllerAdvice )
참고: Spring Exception Handling
참고: Exception Handling과 Response 코드 개선
참고: [스프링부트] @ExceptionHandler를 통한 예외처리
참고: 예외의 종류
참고: Java – Exception Hierarchy
참고: HTTP 상태 코드 정리
참고: 명쾌한 Custom Exception in Java
참고: [Java] NullPointException 원인, 예방, 해결하기
참고: Java 예외처리 try catch
참고: Java | Exception 예외