예외 처리 Exception Handling

프로그램 실행 시 발생할 수 있는 예기치 못한 예외의 발생에 대비한 코드를 작성하는 것.
프로그램의 비정상 종료를 막고, 정상적인 실행 상태를 유지할 수 있도록 한다.
처리되지 못한 예외(Uncaught Exception)는 JVM의 예외처리기(UncaughtExceptionHandler)가 받아서 예외의 원인을 화면에 출력한다.



프로그램 오류

•  컴파일러 에러 Compile-time Error
    컴파일 시에 발생하는 에러
    컴파일러가 소스코드(*.java)에 대해 오타나 잘못된 구문, 자료형 체크 등의 기본적인 검사를 수행하여 오류를 알려준다.
    컴파일을 성공적으로 마치고 나면, 클래스 파일(*.class)이 생성되고, 생성된 클래스 파일을 실행할 수 있게 된다.

•  런타임 에러 Runtime Error
    실행 시에 발생하는 에러
    실행 중에 에러에 의해서 잘못된 결과를 얻거나 프로그램이 비정상적으로 종료되는 것.
    에러 Error :  메모리 부족(OutOfMemoryError), 스택오버플로우(StackOverflowError) 등 발생하면 복구할 수 없는 심각한 오류.
    예외 Exception :  프로그램 코드에 의해서 수습될 수 있는 다소 미약한 오류.



예외 클래스의 계층구조

실행 시 발생할 수 있는 오류(Error, Exception)를 클래스로 정의.
모든 예외의 최고 조상은 Exception 클래스.

Exception RuntimeException
IOException ArithmeticException
DataFormatException ClassCastException
FileNotFoundException NullPointerException
* RuntimeException IndexOutOfBoundException
•  Exception 클래스들
    외부의 영향으로 발생하는 예외(프로그램 사용자의 실수 등)
    Exception 클래스와 그 자손 클래스들(RuntimeException 클래스들 제외)
    - 존재하지 않는 파일의 이름을 입력하거나(FileNotFoundException)
    - 실수로 클래스의 이름을 잘못 적었거나(ClassNotFoundException)
    - 입력한 데이터 형식이 잘못된(DataFormatException) 경우 등

•  RuntimeException 클래스들
    프로그래머의 실수로 발생하는 예외
    RuntimeException 클래스와 그 자손 클래스들
    - 배열의 범위를 벗어나거나(IndexOutOfBoundException)
    - 값이 null인 참조변수의 멤버를 호출하려 했거나(NullPointerException)
    - 클래스간의 형변환을 잘못하거나(ClassCastException)
    - 정수를 0으로 나누는(ArithmeticException) 등




예외 처리하기 try-catch문

발생한 예외의 종류와 일치하는 단 한 개의 catch블럭만 수행된다.
일치하는 catch 블럭이 없으면 예외는 처리되지 않는다.
블럭 내에 포함된 문장이 하나뿐이어도 괄호{}를 생략할 수 없다.
try{
    // 예외 발생 가능성이 있는 문장
}catch(Exception e){
    // e가 발생했을 경우, 이를 처리하기 위한 문장
}



흐름

•  try블럭 내에서 예외가 발생한 경우
    발생한 예외와 일치하는 catch블럭이 있는 지 확인한다.
    일치하는 catch블록을 찾으면, 그 블럭 내의 문장들을 수행하고 전체 try-catch문을 빠져 나와 그 다음 문장을 계속해서 수행한다.
    일치하는 catch블럭을 찾지 못하면, 예외는 처리되지 못한다.
    try블럭에서 예외가 발생한 위치 이후에 있는 문장들은 수행되지 않는다.
    try블럭에 포함시킬 코드의 범위를 잘 선택해야 한다.
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...

        Log.d("TAG", "0");                // 0
	try {
            Log.d("TAG", "1");            // 1
            Log.d("TAG", "error" + 2 / 0);
            Log.d("TAG", "3");
        } catch (Exception e) {
            Log.d("TAG", "4");            // 4
        }
        Log.d("TAG", "5");                // 5
    }
}

•  try블럭 내에서 예외가 발생하지 않은 경우
    catch블럭을 거치지 않고 전체 try-catch문을 빠져 나와 그 다음 문장을 계속해서 수행한다.
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...

        Log.d("TAG", "0");                // 0
	try {
            Log.d("TAG", "1");            // 1
        } catch (Exception e) {
            Log.d("TAG", "2");
        }
        Log.d("TAG", "3");                // 3
    }
}



catch블럭

괄호()와 블럭{} 두 부분으로 나눠져 있다.
괄호() 안에 처리하고자 하는 예외와 같은 타입의 참조변수를 하나 선언한다.
예외가 발생하면, 발생한 예외에 해당하는 클래스의 인스턴스가 만들어진다.
첫 번째 catch블럭부터 차례대로 내려가면서 참조변수의 종류와 생성된 예외 클래스의 인스턴스를 instanceof 연산자를 통해 검사한다.
Exception클래스 타입의 참조변수를 선언하면 어떤 종류의 예외가 발생하더라도 그 catch블럭에 의해 처리되도록 할 수 있다.
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...

        Log.d("TAG", "0");                         // 0
	try {
            Log.d("TAG", "1");                     // 1
            Log.d("TAG", "error" + 2 / 0);
            Log.d("TAG", "3");
        } catch (ArithmeticException e) {
            Log.d("TAG", "ArithmeticException");   // ArithmeticException
	    if(e instanceof ArithmeticException)
                Log.d("TAG", "true");              // true
        } catch (Exception e) {
            Log.d("TAG", "Exception");
        }
        Log.d("TAG", "4");                         // 4
    }
}

예외가 발생했을 때 생성되는 예외 클래스의 인스턴스에는 발생한 예외에 대한 정보가 담겨져 있다.
예외가 여러 개 발생했을 경우 마지막 예외에 대한 내용만 출력된다.

•  printStackTrace()
    예외 발생 당시의 호출스택(Call Stack)에 있었던 메서드의 정보와 예외 메시지를 화면에 출력한다.

•  getMessage()
    발생한 예외 클래스의 인스턴스에 저장된 메시지를 얻을 수 있다.
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...

        Log.d("TAG", "0");                         // 0
	try {
            Log.d("TAG", "1");                     // 1
            Log.d("TAG", "error" + 2 / 0);
            Log.d("TAG", "3");
        } catch (ArithmeticException e) {
            e.printStackTrace();
            Log.d("TAG", e.getMessage());          // divide by zero
        }
        Log.d("TAG", "4");                         // 4
    }
}

•  멀티 catch블럭
    JDK1.7부터 여러 catch블럭을 '|' 기호를 이용하여 하나의 catch블럭으로 합칠 수 있다.
    개수에 제한이 없다.
    멀티 catch블럭에 선언된 참조변수는 상수다.
    두 예외 클래스가 조상과 자손의 관계에 있다면, 조상 클래스만 써주는 것과 똑같기 때문에 컴파일 에러가 발생한다.
try {
    ...
} catch (ParentException | ChildException e) {    // error
    ...
}

try {
    ...
} catch (ParentException e) {
    ...
}


finally블럭

try-catch문과 함께 예외의 발생여부에 상관없이 실행되어야 할 코드를 포함시킬 목적으로 사용한다.
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...

        Log.d("TAG", "0");                         // 0
	try {
            Log.d("TAG", "1");                     // 1
            Log.d("TAG", "error" + 2 / 0);
            Log.d("TAG", "3");
        } catch (ArithmeticException e) {
            Log.d("TAG", "4");                     // 4
        } finally {
  	    Log.d("TAG", "5");                     // 5
	}
        Log.d("TAG", "6");                         // 6
    }
}

return문을 만나도 finally블럭의 문장들은 수행된다.
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...

        Log.d("TAG", "0");                         // 0
	try {
            Log.d("TAG", "1");                     // 1
            return;
        } catch (ArithmeticException e) {
            Log.d("TAG", "3");
        } finally {
  	    Log.d("TAG", "4");                     // 4
	}
        Log.d("TAG", "5");
    }
}



참고 서적: 자바의 정석 3판