1. 예외처리(exception handling)
1.1 프로그램 오류
자바는 실행 시점에 따라 3가지로 에러 타입을 나눈다.
1) 컴파일 에러
컴파일시 발생하는 에러.
클래스 파일(*.class) 이 생성되기 전에 오타나 잘못된 구문, 자료형 체크등의 기본적인 검사를 컴파일러가 수행한다.
2) 논리적 에러
실행은 되나 의도와는 다르게 동작하는 것
3) 런타임 에러
실행시 발생하는 에러
실행시 발생 하는 프로그램 오류를 '에러(Error)' 와 예외(exception) 두가지로 구분한다.
1) 에러(Error) : 프로그램 코드에 의해서 수습될 수 없는 심각한 오류
2) 예외(Exception) : 프로그램 코드에 의해서 수습될 수 있는 다소 미약한 오류
1.2 예외 클래스의 계층 구조
자바에서는 실행 시 발생할 수 있는 오류(Exception과 Error) 를 클래스로 정의 하였다.
모든 예외의 최고 조상은 Exception 클래스이며 상속계층도를 Exception 클래스부터 도식화 하면 아래와 같다.
위 그림에서 볼 수 있듯이 예외클래스들은 다음과 같이 두그룹으로 나눠질 수 있다.
1) Exeption 클래스와 그 자손들 - 사용자의 실수와 같은 외적인 요인에 의해 발생하는 예외
2) RuntimeException 클래스와 그 자손들 - 프로그래머의 실수로 발생하는 예외
RuntimeException 클래스들은 주로 프로그래머의 실수에 의해서 발생될 수 있는 예외들로 자바의 프로그래밍 요소들과 관계가 깊다.
예를들어
- 배열 범위를 벗어날 경우 (ArrayyIndexOutOfBoundException)
- 값이 null 인 참조 변수의 멤버를 호출할 경우 (NullPointerException)
- 클래스간의 형변환을 잘못할 경우(ClassCastException)
- 정수를 0 으로 나누려고 하는경우(ArithmeticException)
그 외에 Exception 클래스 들은 주로 외부의 영향으로 발생할 수 있는 것들로. 프로그램의 사용자들의 동작에 의해서 발생 하는 경우가 많다.
예를 들어
- 존재하지 않는 파일의 이름을 입력했을 경우 (FileNotFoundException)
- 실수로 클래스의 이름을 잘못 적었을 경우 (ClassNotFoundException)
- 입력한 데이터 형식이 잘못된 경우(DataFormatException)
1.3 예외처리하기 - try-catch 문
예외처리(exception handiling)란
정의 - 프로그램 실행 시 발생할 수 있는 예외에 대비한 코드를 작성하는 것
목적 - 프로그램의 비정상 종료를 막고, 정상적인 실행상태를 유지하는 것
|참고| 에러와 예외는 모두 실행 시(runtime) 발생하는 오류이다.
발생한 예외를 처리하지 못하면 프로그램은 비정상적으로 종료되며 처리되지 못한 예외(uncaught exception) 는 JVM의 '예외처리기(UncauthgtExceptionHandler)' 가 받아서 예외의 원인을 화면에 출력한다.
예외를 처리하기 위해서는 try-catch 문을 사용하며 그 구조는 다음과 같다.
try {
// 예외가 발생할 가능성이 있는 문장들을 넣는다.
} catch (Exception1 e1) {
// Exception1이 발생했을 경우, 이를 처리하기 위한 문장을 적는다.
} catch (Exception2 21) {
// Exception2가 발생했을 경우, 이를 처리하기 위한 문장을 적는다.
} catch (ExceptionN eN) {
// ExceptionN이 발생했을 경우, 이를 처리하기 위한 문장을 적는다.
}
printStackTrace() 와 getMesage()
printStackTrace() 예외발생 당시의 호출스택(Call Stack) 에 있었던 메서드의 정보와 예외 메시지를 화면에 출력한다.
getMessage() 발생한 예외클래스의 인스턴스에 저장된 메시지를 얻을 수 있다.
1.4 예외 발생 시키기
1.4.1 예외 발생 시키기
키워드 throw 를 사용해서 프로그래머가 고의로 예외를 발생시킬 수 있으며, 방법은 아래의 순서를 따르면 된다.
1) 먼저, 연산자 new 를 이용해서 발생시키려는 예외 클래스의 객체를 만든 다음
Exception e = new Exception("고의로 발생시켰음");
2) 키워드 throw 를 이용해서 예외를 발생시킨다.
throw e;
public class ExceptionEx9 {
public static void main(String[] args) {
try {
Exception e = new Exception("고의로 발생시켰음.");
throw e;
// throw new Exception("고의로 발생시켰음.");
}catch (Exception e) {
System.out.println("에러메시지 : " + e.getMessage());
}
System.out.println("프로그램이 정상 종료되었음.");
}
}
Exception 인스턴스를 생성할때, 생성자에 String을 넣어주면, 이 String 이 Exception 인스턴스에 메시지로 저장된다.
이 메시지는 getMessage()를 이용해서 얻을 수 있다.
1) 컴파일 에러가 발생 하는 예외
package exception;
public class ExceptionEx10 {
public static void main(String[] args) {
throw new Exception();
}
}
- 예외처리가 되어야 할 부분에 예외처리가 되어 있지 않다는 에러이다.
- Exception 클래스들(Exception 클래스와 그 자손들)' 이 발생할 가능성이 있는 문장들에 대해 예외처리를 해주지 않으면 컴파일조차 되지 않는다.
2) 컴파일 에러가 발생하지 않은 예외 but 런타임 에러 발생
package exception;
public class ExceptionEx10 {
public static void main(String[] args) {
throw new RuntimeException();
}
}
- RuntimeException와 그 자손(RuntimeException 클래스들)' 에 해당하는 예외는 프로그래머에 의해 실수로 발생하는 것들이기 때문에 예외처리를 강제하지 않는 것이다.
- 컴파일러가 예외처리를 확인하지 않는 RuntimeException 클래스들은 'unchecked 예외' 라고 부르고, 예외처리를 확인하는 Exception 클래스들은 'checked 예외' 라고 부른다.
1.5 메서드에 예외 선언하기
1.5.1
예외를 처리하는 방법에는 지금까지 배워온 try-catch 문을 사용하는 것 외에, 예외를 메서드에 선언하는 방법이 있다.
throw 로 추가, 예외가 여러개일 경우 쉼표(,) 로 구분한다.
void method() throw Exception1, Exception2, .... ExceptionN{
// 메서드의 내용
}
1.5.2
만일 아래와 같이 모든 예외의 최고 조상인 Exception 클래스를 메서드에 선언하면, 이 메서드는 모든 종류의 예외가 발생할 가능성이 있다는 뜻이다.
void method() throws Exception {
// 메서드의 내용
}
1.5.3
java API 문서를 통해 사용하고자 하는 메서드의 선언부와 "Throws:' 를 보고, 이 메서드에서는 어떤 예외가 발생할 수 있으며 반드시 처리해주어야 하는 예외는 어떤 것들이 있는지 확인하는 것이 좋다.
1.5.4
사실 예외 메서드의 throws 에 명시하는 것은 예외를 처리하는 것이 아니다.
자신(예외가 발생할 가능성이 있는 메서드)을 호출한 메서드에게 예외를 전달하여 예외처리를 떠맏기는 것이다.
예외를 전달받은 메서드가 또 자신을 호출한 메서드에게 전달할 수 있으며, 이런식으로 계속 호출 스택에 있는 메서드들을 따라가다가 제일 마지막에 있는 main 메서드에서도 예외 처리가 되지 않으면, main 메서드 마저 종료되어 프로그램이 전체가 종료된다.
책의 예제에서는 아래의 코드를 실행했을때 콘솔창의 결과로 3가지를 확인 할수 있다고 한다.
public class ExceptionEx10 {
public static void main(String[] args){
method1();
}
private static void method1() throws Exception{
method2();
// method1의 끝
}
private static void method2() throws Exception{
throw new Exception();
// method2의 끝
}
}
java.lang.Exception
at ExceptionExl2.method2(ExceptionExl2.java:11)
at ExceptionExl2.methodl(ExceptionExl2.java:7)
at ExceptionExl2.main(ExceptionExl2.java:3)
1. 예외가 발생했을때, 모두 3개의 메서드(main, mehod1, medhoe2) 가 호출 스택에 있었으며
2. 예외가 발생한 곳은 제일 윗줄에 있는 method2() 라는 것과
3. main 메서드가 method1()을, 그리고 mehod1()은 mehod2() 를 호출 했다는 것을 알 수 있다.
하지만 실제 컴파일에서는 같은 맥락이지만 다른 결과가 나온다. 일단 실행이 되지않는다.
(근데 책의 내용대로라면 당연히 Exception 클래스를 throw 하면 실행이 되지 않나?)
- 위 코드처럼 작성될 경우 상단 코드에 Exception 을 처리하도록 경고가 뜬다.
- 위의 사진처럼 try- catch 문을 작성할 경우 성공적으로 실행이 된다.
- 반대로 위 코드처럼 함수에 throw 를 추가할 경우에는 에러가 발생한다.
또 다른 예제를 보자,
아래 두가지 타입의 예외 처리 방식이 있다.
- Example case 1. 예외를 호출한 곳에서 처리하기
public class ExceptionEx13 {
public static void main(String[] args) {
method1();
}
static void method1() {
try {
throw new Exception();
}catch (Exception e) {
System.out.println("method1 메서드에서 예외가 처리되었습니다.");
e.printStackTrace();
}
// method1의 끝
}
}
예외가 발생한 메서드에서 예외를 처리하지 않고 호출한 메서드로 넘겨주면, 호출한 메서드에서는 method1() 을 호출한 라인에서 예외가 발생한 것으로 간주되어 이에 대한 처리를 하게 된다.
- Example case 2. 예외를 생성한 곳에서 처리하기
public class ExceptionEx13 {
public static void main(String[] args) {
method1();
}
static void method1() {
try {
throw new Exception();
}catch (Exception e) {
System.out.println("method1 메서드에서 예외가 처리되었습니다.");
e.printStackTrace();
}
// method1의 끝
}
}
위 코드는 method1() 에서 예외처리를 했고, 이렇게 처리되어지면 호출한 메서드 (main 메서드) 에서는 예외가 발생했다는 사실 조차 모르게 된다.
이처럼 예외가 발생한 메서드(method1) 혹은 호출한 메서드(main) 에서 처리할 수도 있으며 이 두 메서드가 예외처리를 분담 할 수도 있다.
1.6 자동 자원 반환 - try-with-resources 문
1.6.1
JDK1.7 부터 try-with-resources 문이라는 try-catch 문의 변형이 새로 추가되었다.
기존 try-catch-finally 에서 파일을 중단하는 close() 로직을 개선한 것으로 try-with-resources 문의 괄호() 안에 객체를 생성하는 문장을 넣으면, 이 객체는 따로 close() 를 호출하지 않아도 try 블록을 벗어나는 순간 자동적으로 close() 가 호출된다. 그 다음에 catch 블럭 또는 finally 블럭이 수행된다.
* 이처럼 try-with-reseouce 문에 의해 자동으로 객체의 close() 가 호출될 수 있으려면 클래스가 AutoCloseable 이라는 인터페이스를 구현한 것이어야만 한다.
public interface AutoCloseable {
void close() throws Exception;
}
1.7 사용자정의 예외 만들기
기존의 정의된 예외 클래스 외에 필요에 따라 프로그래머가 새로운 예외 클래스를 정이하여 사용할 수 있다.
보통 Exception 클래스 또는 RuntimeException 클래스로부터 상속받아 클래스를 만들지만, 필요에 따라서 예외 클래스를 선택할 수 있다.
public class MyException extends Exception {
// 에러 코드 값을 저장하기 위한 필드를 추가
private final int ERR_CODE;
public MyException(String message, int err_code) { // 생성자
super(message);
ERR_CODE = err_code;
}
public MyException(String message) { // 생성자
this(message, 100); // ERR_CODE 를 100(기본값)으로 초기화한다.
}
public int getErrCode() { // 에러 코드를 얻을 수 있는 메서드도 추가했다.
return ERR_CODE; // 이 메서드는 주로 getMessage()와 함께 사용 될 것이다.
}
}
- 이전 코드를 개선하여 ERR_CODE 와 getErrCode()를 MyException 클래스의 멤버로 추가 했다.
- 이렇게 함으로써 MyException이 발생했을 때, catch 블럭에서 getMessage()와 getErrCode()를 사용해서 에러코드와
메시지를 모두 얻을 수 있다.
1.8 예외 되던지기(exception re-throwing)
한 메서드에서 발생할 수 있는 예외가 여럿인 경우, 몇개는 try-catch문을 통해서 메서드 내에서 자체적으로 처리하고, 그 나머지는 선언부에 지정하여 호출한 메서드에서 처리하도록 함으로써, 양쪽에서 나눠서 처리되도록 할 수 있다.
심지어는 단 하나의 예외에 대해서도 예외가 발생한 메서드와 호출한 메서드, 양쪽에서 처리하도록 할 수 있다.
이것은 예외를 처리한 후에 인위적으로 다시 발생시키는 방법을 통해서 가능한데, 이것을 '예외 되던지기(exception re-throwing)' 라고 한다.
public class Exception17 {
public static void main(String[] args) {
try {
method1();
} catch (Exception e) {
System.out.println("main 메서드에서 예외가 처리되었습니다. ");
}
}
static void method1() throws Exception {
try {
throw new Exception();
} catch (Exception e) {
System.out.println("method1 메서드에서 예외가 처리되었습니다.");
throw e;
}
} // method1 메서드의 끝
}
- 이 방법은 하나의 예외에 대해서 예외가 발생한 메서드와 이를 호출한 메서드 양쪽 모두에서 처리해줘야 할 작업이 있을때 사용된다. 이 때 주의할 점은 예외가 발생할 메서드에서는 try-catch문을 사용해서 예외처리를 해줌과 동시에 메서드의 선언부에 발생한 예외를 throws 에 지정해줘야 한다는 것이다.
1.9 연결된 예외(chained exception)
1.9.1
한 예외가 다른예외를 발생시킬 수 도 있다. 예를 들어 예외 A가 예외 B를 발생시켰다면, A를 B의 '원인 예외(cause exception)' 라고 한다.
try {
startinstall(); // spaceException 발생
copyFiles();
}catch(SpaceException e){
// SpaceException 발생
InstallException ie = new InstallException ( "설치중 예외발생 II ) ; // 예외 생성
ie.initCause(e); // InstallException 의 원인 예외를 SpaceException으로 지정
throw ie ; // InstallE ception을 발생시킨다.
} catch (MemoryException me) {
...
- Throwable initCause(Throwable cause) 지정한 예외를 원인 예외로 등록
initCause() 는 Exception 클래스의 조상인 Trhowable 클래스에 정의되어 있기 때문에 모든 예외에서 사용가능하다.
- Throwable getCause() 원인 예외를 반환
1.9.2 사용 이유
여러가지 예외를 하나의 큰 분류의 예외로 묶어서 다루기 위해서 이다.
또 다른 이유는 checked 예외를 unchecked 예외로 바꿀수 있도록 하기 위해서이다.
'java' 카테고리의 다른 글
[JAVA] 자바의 정석 - Stream (0) | 2021.10.31 |
---|---|
[JAVA] JVM 에 대하여 ( + Garbage Collection) (0) | 2021.10.29 |
[JAVA] Concurrent Collections - ConcurrentHashMap() (0) | 2021.10.11 |
[JAVA] Map 정리(HashMap , TreeMap , Red-Black Tree ) (0) | 2021.10.11 |
SOLID(객체지향설계) - 코드 예제, 리팩토링 (0) | 2021.09.11 |