해당 게시물은 자바의 정석을 정리한 내용 입니다.
1.1 프로그램 오류
프로그램이 실행 중 어떤 원인에 의해서 오작동을 하거나 비정상적으로 종료되는 경우가 있다.
이러한 결과를 초래하는 원인을 프로그램 에러 또는 오류라고 한다.
프로그램 오류는 컴파일 에러(compile-time error), 런타임 에러(runtime error), 논리적 에러(logical error)로 나눌 수 있다.
컴파일 에러 : 컴파일 시에 발생하는 에러
런타임 에러 : 실행 시에 발생하는 에러
논리적 에러 : 실행은 되지만, 의도와 다르게 동작하는 것
Java의 런타임 에러는 에러(error)와 예외(exception), 두 가지로 구분 된다.
에러(error) : 프로그램 코드에 의해서 수습될 수 없는 심각한 오류
예외(exception) : 프로그램 코드에 의해서 수습될 수 있는 다소 미약한 오류
1.2 예외 클래스의 계층 구조
자바에서는 실행 시 발생할 수 있는 오류(Exception과 Error)를 클래스로 정의하였다.
모든 예외의 최고 조상은 Exception 클래스이며, 상속 계층도를 Exception 클래스 부터 도식화하면 다음과 같다.
예외 클래스들은 두 그룹으로 나눠질 수 있다.
(1) Exception 클래스와 그 자손들 (RuntimeException과 자손들 제외)
(2) RuntimeException 클래스와 그 자손들
(1) Exception 클래스들은 사용자가 발생하는 예외
FileNotFoundException : 존재하지 않는 파일의 이름을 입력하는 경우
ClassNotFoundException : 클래스의 이름을 잘못 적은 경우
DataFormatException : 입력된 데이터 형식이 잘못된 경우
(2) RuntimeException 클래스들은 프로그래머의 실수로 발생하는 예외
IndexOutofBoundsException : 배열의 범위를 벗어나는 경우
NullPointerException : 값이 null인 참조변수의 멤버를 호출하는 경우
ClassCastException : 클래스 간의 잘못된 형 변환을 하는 경우
ArithmeticException : 산술 계산 예외
Ex) 정수를 0으로 나누는 경우
Exception 클래스들은 컴파일러가 예외 처리 여부를 확인하는 'checked 예외'이다. (예외 처리가 필수)
그리고 RuntimeException 클래스들은 컴파일러가 예외 처리 여부를 확인하지 않는 'unchecked' 예외이다. (예외 처리가 선택 → 컴파일 에러가 발생 X)
1.3 예외 처리 하기 (try - catch 문)
예외 처리(exception handling)
정의 - 프로그램 실행 시 발생할 수 있는 예외에 대비한 코드를 작성하는 것
목적 - 프로그램의 비정상 종료를 막고, 정상적인 실행 상태를 유지하는 것
발생한 예외를 처리하지 못하면, 프로그램은 비정상적으로 종료되며,
처리되지 못한 예외(uncaught exception)는 JVM의 예외 처리기(UncaughtExceptionHandler)가 받아서
예외의 원인을 화면에 출력한다.
try{
// 예외가 발생할 수 있는 문장들을 넣는다.
}catch(Exception1 e1){
// Exception1이 발생했을 경우, 이를 처리하기 위한 문장을 넣는다.
}catch(Exception2 e2){
// Exception2이 발생했을 경우, 이를 처리하기 위한 문장을 넣는다.
}catch(ExceptionN eN){
// ExceptionN이 발생했을 경우, 이를 처리하기 위한 문장을 넣는다.
}
하나의 try 블럭 다음에는 여러 종류의 예외를 처리 할 수 있도록 하나 이상의 catch 블럭이 올 수 있으며,
이 중 발생한 예외의 종류와 일치하는 단 한 개의 catch 블럭만 수행 된다.
발생한 예외의 종류와 일치하는 catch 블럭이 없으면 예외는 처리 되지 않는다.
class ExceptionEx3 {
public static void main(String args[]) {
int number = 100;
int result = 0;
for(int i=0; i < 10; i++) {
try {
result = number / (int)(Math.random() * 10);
System.out.println(result);
} catch (ArithmeticException e) {
System.out.println("0");
} // try-catch의 끝
} // for의 끝
}
}
위의 코드는 ArithmeticException에 해당하는 catch 블럭을 찾아서 그 catch 블럭 내의 문장들을
실행한 다음 try-catch 문을 벗어나 for문의 다음 반복을 계속 수행하여 작업을 모두 마치고
정상적으로 종료되었다.
if문과 달리, try 블럭이나 catch 블럭 내에 포함된 문장이 하나뿐이어도 중괄호{}를 생략 할 수 없다.
1.4 try-catch 문에서의 흐름
try-catch문에서 예외가 발생한 경우와 발생하지 않았을 때의 흐름(문장의 실행 순서)이 달라지는데,
아래에 이 두 가지 경우에 따른 문장 실행 순서를 정리 하였다.
[try 블럭 내에서 예외가 발생한 경우]
(1) 발생한 예외와 일치하는 catch 블럭이 있는지 확인한다.
(2) 일치하는 catch 블럭을 찾게 되면, 그 catch 블럭 내의 문장들을 수행하고
전체 try-catch 문을 빠져나가서 그 다음 문장을 계속해서 수행한다.
만일 일치하는 catch 블럭을 찾지 못하면, 예외는 처리되지 못한다.
[try 블럭 내에서 예외가 발생하지 않은 경우]
catch 블럭을 거치지 않고 전체 try-catch 문을 빠져 나가서 수행을 계속한다.
class ExceptionEx5 {
public static void main(String args[]) {
System.out.println(1);
System.out.println(2);
try {
System.out.println(3);
System.out.println(0/0); // 0으로 나누기 금지 (예외 발생)
System.out.println(4); // 실헹되지 않는다.
} catch (ArithmeticException ae) {
System.out.println(5);
} // try-catch의 끝
System.out.println(6);
} // main 메서드의 끝
}
try 블럭에서 예외가 발생하면, 예외가 발생한 위치 이후에 있는 try 블럭의 문장들은 수행되지 않으므로,
try 블럭에 포함시킬 코드의 범위를 잘 선택해야 한다.
1.5 예외의 발생과 catch 블럭
예외의 발생과 catch 블럭
1) 예외가 발생하면 발생한 예외에 해당하는 클래스의 인스턴스가 만들어진다.
2) 예외가 발생한 문장이 try블럭에 포함되어 있다면, 이 예외를 처리 할 수 있는 catch 블럭을 찾게된다.
3) 첫번째 catch 블럭부터 차례로 내려가면서 catch 블럭의 괄호()내에 선언된 참조변수의 종류와 생성된
예외클래스의 인스턴스에 instanceof 연산자를 이용해서 검사하고 결과가 true이면 예외처리한다.
검사 결과가 true인 catch 블럭이 하나도 없으면 예외는 처리되지 않는다.
class ExceptionEx6 {
public static void main(String args[]) {
System.out.println(1);
System.out.println(2);
try {
System.out.println(3);
System.out.println(0/0); // 0으로 나눠서 고의로 ArithmeticException를 발생시킨다.
System.out.println(4); // 실행되지 않는다.
} catch (Exception e) { // ArithmeticException대신 Exception을 사용.
System.out.println(5);
} // try-catch의 끝
System.out.println(6);
} // main메서드의 끝
}
printStackTrace()와 getMessage()
- 예외가 발생하면 예외 객체가 만들어진다.
- 발생한 예외 객체를 catch 블럭의 참조 변수로 접근할 수 있다.
printStackTrace() : 예외 발생 당시의 호출스택(Call Stack)에 있었던 메서드의 정보와 예외 메시지를 화면에 출력한다.
getMessage() : 발생한 예외 클래스의 인스턴스에 저장된 메시지를 얻을 수 있다.
멀티 catch 블럭
- JDK 1.7부터 여러 catch블럭을 '|' 기호를 이용해서, 하나의 catch블럭으로 합칠 수 있게 되었으며
이를 '멀티 catch 블럭' 이라 한다.
즉, 내용이 같은 catch 블럭을 하나로 합친 것이다.
- '|' 기호로 연결할 수 있는 예외 클래스의 개수에는 제한이 없다.
- 아래 코드를 '|'를 이용하여 변경 할 수 있다.
try {
...
}catch (ExceptionA e){
e.printStackTrace();
}catch (ExceptionB e2){
e2.printStackTrace();
}
try {
...
}catch (ExceptionA | ExceptionB e){
e.printStackTrace();
}
조상과 자손의 관계에 있는 catch 블럭을 멀티 catch 블럭으로 합칠 수 없다. (컴파일 에러가 발생)
멀티 catch 블럭의 참조변수로는 공통 멤버만 사용 할 수 있다.
1.6 예외 발생 시키기
키워드 throw를 사용해서 프로그래머가 예외를 발생시킬 수 있다.
1) 먼저, 연산자 new를 이용해서 발생시키려는 예외 클래스의 객체를 만든 다음
Exception e = new Exception("고의로 발생시켰음");
2) 키워드 throw를 이용해서 예외를 발생시킨다.
throw e;
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());
e.printStackTrace();
}
System.out.println("프로그램이 정상 종료되었음.");
}
}
Exception 인스턴스를 생성할 때, 생성자에 String을 넣어 주면, 이 String이 Exception 인스턴스에 메시지로 저장된다.
class ExceptionEx11 {
public static void main(String[] args) {
throw new RuntimeException(); // RuntimeException을 고의로 발생시킨다.
}
}
Exception 클래스와 그 자손들이 발생할 가능성이 있는 문장들에 대해 예외처리를 해주지 않으면 컴파일 조차 되지 않는다.
RuntimeException 클래스와 그 자손에 해당하는 예외는 프로그래머에 의해 실수로 발생하는 것들이기 때문에 예외처리를 강제하지 않는다.
1.7 메서드에 예외 선언하기
- 예외를 처리하는 방법에는 try-catch 문을 사용하는 것 외에, 예외를 메서드에 선언하는 방법이 있다.
- 메서드에 예외를 선언하려면, 메서드의 선언부에 키워드 throws를 사용해서 메서드 내에서 발생할 수 있는 예외를 적는다.
그리고 예외가 여러 개인 경우 쉼표(,)로 구분한다.
void method() throws Exception1, Exception2, ... ExceptionN {
// 메서드의 내용
}
- 만약 아래와 같이 모든 예외의 최고 조상인 Exception 클래스를 메서드에 선언하면, 이 메서드는 모든 종류의 예외가 발생할 가능성이 있다는 뜻이다.
void method() throws Exception {
// 메서드의 내용
}
- 메서드의 throws에 예외를 명시하는 것은 예외를 처리하는 것이 아니라, 자신을 호출한 메서드에게 예외를 전달하여 예외 처리를 떠맡기는 것이다.
class ExceptionEx12 {
public static void main(String[] args) throws Exception {
method1(); // 같은 클래스내의 static멤버이므로 객체생성없이 직접 호출가능.
} // main메서드의 끝
static void method1() throws Exception {
method2();
} // method1의 끝
static void method2() throws Exception {
throw new Exception();
} // method2의 끝
}
위의 결과로 부터 다음과 같은 사실을 알 수 있다.
1) 예외가 발생했을 때 모두 3개의 메서드가 호출스택에 있었으며,
2) 예외가 발생한 곳은 제일 윗줄에 있는 method2() 라는 것과
3) main메서드가 method1()을 그리고 method1()은 method2()를 호출했다는 것을 알 수 있다.
1.8 finally 블럭
- finally 블럭은 예외의 발생 여부와 관계없이 실행되어야 하는 코드를 넣는다.
- 선택적으로 사용할 수 있으며, try-catch-finally의 순서로 구성된다.
- 예외가 발생한 경우에는 try → catch → finally의 순서로 실행되고
예외 미발생시, try → finally의 순서로 실행된다.
- try 또는 catch블럭에서 return문을 만나도 finally블럭은 수행된다.
try {
// 예외가 발생할 가능성이 있는 문장들을 넣는다.
} catch(Exception1 e1){
// 예외 처리를 위한 문장을 적는다.
} finally {
// 예외의 발생 여부에 관계 없이 항상 수행되어야 하는 문장들을 넣는다.
// finally 블럭은 try-catch 문의 맨 마지막에 위치 해야 한다.
}
class FinallyTest {
public static void main(String args[]) {
try {
startInstall(); // 프로그램 설치에 필요한 준비를 한다.
copyFiles(); // 파일들을 복사한다.
deleteTempFiles(); // 프로그램 설치에 사용된 임시파일들을 삭제한다.
} catch (Exception e) {
e.printStackTrace();
deleteTempFiles(); // 프로그램 설치에 사용된 임시파일들을 삭제한다.
} // try-catch의 끝
} // main의 끝
static void startInstall() {
/* 프로그램 설치에 필요한 준비를 하는 코드를 적는다.*/
}
static void copyFiles() { /* 파일들을 복사하는 코드를 적는다. */ }
static void deleteTempFiles() { /* 임시파일들을 삭제하는 코드를 적는다.*/ }
}
1.9 자동 자원 반환 - try with resources 문
- JDK 1.7 부터 try with resources 문이라는 try-catch 문의 변형이 추가 되었다.
- try with resources 문의 괄호 () 안에 객체를 생성하는 문장을 넣으면, try 블럭을 벗어나는 순간 자동적으로 close()가 호출된다.
그 다음에 catch 블럭 또는 finally 블럭이 수행된다.
try ( FileInputStream fis = new FileInputStream("score.dat");
DataInputStream dis = new DataInputStream(fis)){
while(true){
score = dis.readInt();
System.out.println(score);
sum += score;
} catch(EOFException e){
System.out.println("점수의 총합은 " + sum + "입니다.");
} catch(IOException ie){
ie.printStackTrace();
}
}
- try with resources 문에 의해 자동으로 객체의 close()가 호출될 수 있으려면 클래스가 AutoCloseable이라는 인터페이스를 구현한 것이어야만 한다.
1.10 사용자 정의 예외 만들기
- 기존의 예외 클래스를 상속 받아서 새로운 예외 클래스를 정의할 수 있다.
(보통 Exception 또는 RuntimeException 클래스를 상속 받음)
- Exception 클래스는 생성 시에 String 값을 받아서 메시지로 저장할 수 있다.
사용자 정의 예외 클래스도 메시지를 저장 할 수 있으려면, 아래 코드와 같이 String을 매개변수로 받는 생성자를 추가해주어야 한다.
class MyException extends Exception {
MyException(String msg){ // 문자열을 매개변수로 받는 생성자
super(msg); // 조상인 Exception 클래스의 생성자를 호출한다.
}
}
- 기존의 예외 클래스를 상속 받아서 새로운 예외 클래스를 정의할 수 있다.
class MyException extends Exception {
private final int ERR_CODE; // 에러 코드 값을 저장하기 위한 필드를 추가 했다.
MyException(String msg, int errCode){ // 생성자
super(msg);
ERR_CODE = errCode; // 생성자를 통해 초기화 한다.
}
MyException(String msg){ // 생성자
this(msg, 100); // ERR_CODE를 100 (기본 값)으로 초기화한다.
}
public int getErrCode(){ // 에러 코드를 얻을 수 있는 메서드도 추가했다.
return ERR_CODE; // 이 메서드는 주로 getMessage()와 함께 사용될 것이다.
}
}
1.11 예외 되던지기(exception re-throwing)
- 예외 되던지기는 예외를 처리한 후에 다시 예외를 발생시켜서 호출한 메서드로 전달하는 것이다.
- 예외가 발생한 메서드와 이를 호출한 메서드, 양쪽에서 예외를 처리해야 하는 경우에 사용한다.
class ExceptionEx17 {
public static void main(String[] args)
{
try {
method1();
} catch (Exception e) {
System.out.println("main메서드에서 예외가 처리되었습니다.");
}
} // main메서드의 끝
static void method1() throws Exception {
try {
throw new Exception();
} catch (Exception e) {
System.out.println("method1메서드에서 예외가 처리되었습니다.");
throw e; // 다시 예외를 발생시킨다.
}
} // method1메서드의 끝
}
위의 코드는 method1()과 main 메서드 양쪽의 catch 블럭이 모두 수행되었음을 알 수 있다.
method1()의 catch 블럭에서 예외를 처리하고도 throw문을 통해 다시 예외를 발생 시켰다.
그리고 이 예외를 main 메서드 한 번 더 처리하였다.
- 반환 값이 있는 return문의 경우, catch 블럭에도 return 문이 있어야 한다.
예외가 발생했을 경우에도 값을 반환해야하기 때문이다.
static int method1() {
try {
System.out.println("method1()이 호출되었습니다.");
return 0; // 현재 실행 중인 메서드를 종료한다.
} catch(Exception e){
e.printStackTrace();
return 1; // catch 블럭 내에도 return문이 필요하다.
} finally{
System.out.println("method1()의 finally 블럭이 실행되었습니다.");
}
}
- catch 블럭에서 예외 되던지기를 해서 호출한 메서드로 예외를 전달하면, return 문이 없어도 된다.
1.12 연결된 예외(chained exception)
- 한 예외가 다른 예외를 발생 시킬 수 있다.
- 예외 A가 예외 B를 발생시켰다면, A를 B의 ‘원인 예외(cause exception)’라고 한다.
Throwable initCause(Throwable cause) : 지정한 예외를 원인 예외로 등록
Throwable getCause() : 원인 예외를 반환
- 아래 코드는 SpaceException을 원인 예외로 하는 InstallException을 발생시키는 방법을 보여준다.
먼저 InstallException을 생성한 후에, initCause()로 SpaceException을 InstallException의 원인 예외로 등록한다.
그리고 'throw'로 이 예외를 던진다.
try {
startInstall(); // SpaceException 발생
copyFiles();
} catch (SpaceException e) {
InstallException ie = new InstallException(“설치 중 예외 발생”); // 예외 생성
ie.initCause(e); // InstallException의 원인 예외를 SpaceException으로 지정
throw ie; // InstallException을 발생시킨다.
} catch (MemoryException me) {
. . .
- 원인 예외로 등록해서 다시 예외를 발생시키는 이유
1) 여러 예외를 큰 분류의 예외로 묶을 때, 연결된 예외로 처리한다.
2) 필수 예외(Exception과 그 자손들)를 선택 예외(RuntimeException과 그 자손들)로 변경할 때 연결된 예외로 처리한다.
static void startInstall() throws SpaceException{
if(!enoughSpace()) // 충분한 설치 공간이 없으면
throw new SpaceException("설치할 공간이 부족합니다.");
if(!enoughMemory()) // 충분한 메모리가 없으면
throw new RuntimeException(new MemoryException("메모리가 부족합니다. "));
}
MemoryException은 Exception의 자손이므로 반드시 예외를 처리해야하는데, 이 예외를 RuntimeException으로 감싸버렸기 때문에
unchecked 예외가 되었다. 참고로 위의 코드에서는 initCause() 대신 RuntimeException의 생성자를 사용 했다.
RuntimeException(Throwable cause) // 원인 예외를 등록하는 생성자
'Java > 객체 지향 개념 ~' 카테고리의 다른 글
자바의 정석 (Chapter 10. 날짜와 시간 & 형식화) (0) | 2020.08.19 |
---|---|
자바의 정석 (Chapter 9. java.lang 패키지와 유용한 클래스) (0) | 2020.08.14 |
자바의 정석 (Chapter 7_3. 객체지향개념 2) (0) | 2020.08.03 |
자바의 정석 (Chapter 7_2. 객체지향개념 2) (0) | 2020.08.02 |
자바의 정석 (Chapter 7_1. 객체지향개념 2) (0) | 2020.08.02 |
댓글