ㅁ 자바에서 예외처리를 if문으로 할 수 도 있지만 그렇게 하면, 프로그램의 주 흐름을 구성하는 코드와 예외상황을 처리하는 코드의 구분이 어려워진다. 그래서 try ~ catch 기반의 예외처리 방식을 사용한다.
ㅁ try와 catch는 각각이 별도의 문장처럼 느껴지는데, 이 둘은 하나의 문장이다. 때문에 try와 catch 사이에 다른 문장이 삽입될 수 없다.
- try 영역에서 발생한 ~에 해당하는 예외상황은 이어서 등장하는 catch 영역에서 처리된다.
- catch 영역에서 모든 예외상황을 처리하는 것은 아니다.
catch 영역의 매개변수 선언이 있는데, 이 부분에 명시되어 있는 유형의 예외상황만 처리가 가능하다.
ㅁ
- 1. 자바 가상머신이 try에서 0으로 나누는 예외상황이 발생했음을 인식
- 2. 이 상황을 위해 정의된 ArithmeticException 클래스의 인스턴스를 생성
- 3. 이렇게 생성된 인스턴스의 참조 값을 catch영역에 선언된 매개변수에 전달
- 즉 catch는 메서드와 그 형태가 유사하여 인스턴스의 참조 값을 인자로 전달받을 수 있기 때문에, 자바 가상머신은 예외상황 발생시 참조 값의 전달을 통해서 catch 영역으로 실행을 이동시키게 된다.
ㅁ try 영역에서 예외상황이 발생한 문장의 나머지 부분을 건너뛰기 때문에 try 영역의 구성 범위도 적절해야 한다.
ㅁ finally를 놓치기 쉬운데, 실행의 흐름이 try 영역으로 진입하면 try~catch문을 빠져나가는 마지막 순간에 무조건 실행된다.
중간에 return을 하더라도 finally 영역은 실행되고 나서 메서드를 빠져나가게 된다.
ㅁ Exception 클래스는 앞서 모든 예외 클래스가 상속한다는 Throwable 클래스의 하위 클래스이다.
- Throwable 클래스를 직접 상속하지 않고 Exception 클래스를 상속하는 이유는 나중에.
일단은 Exception 클래스를 상속해서 예외 클래스를 정의한다고 기억하기 바란다.
ㅁ 프로그래머가 직접 정의하는 예외 상황
- ProgrammerDefineException.java
import java.util.Scanner;
class AgeInputException extends Exception { // Exception 클래스를 상속하므로 이는 예외 클래스이다.
public AgeInputException(){
super("유효하지 않은 나이가 입력되었습니다."); // 상위 클래스 Exception 클래스의 생성자 호출.
}
}
class ProgrammerDefineException {
public static void main(String[] args)
{
System.out.print("나이를 입력하세요 : ");
try {
int age = readAge();
System.out.println("당신은 " + age + "세입니다.");
}
catch(AgeInputException e) {
System.out.println(e.getMessage());
}
}
public static int readAge() throws AgeInputException {
Scanner keyboard = new Scanner(System.in);
int age = keyboard.nextInt();
if(age<0)
{
AgeInputException excpt = new AgeInputException(); // 예외 클래스의 인스턴스 생성
throw excpt;
}
return age;
}
}
[실행 결과]
나이를 입력하세요 : -2
유효하지 않은 나이가 입력되었습니다.
- thorw는 예외상황이 발생되었음을 자바 가상머신에게 알리는 키워드.
이 문장이 실행되면 자바의 예외처리 매커니즘이 동작하게 된다.
- 예외상황의 처리는 예외상황이 발생한(throw문이 존재하는) 메서드를 호출한 지점으로 넘어가게 된다.
이렇게 예외에 대한 처리가 넘어간다는 것은 throws를 이용해서 명시해야 한다.
- 메서드를 호출한 영역으로 예외가 던져졌다는 뜻은 메서드를 호출한 영역으로 예외상황에 대한 처리가 넘겨졌다는 뜻이다.
- throw excpt;를 했을 때, 프로그램의 실행 흐름이 throws AgeInputException이라고 예외 클래스로 넘어가는게 아니고
- throw excpt; 문장이 실행될 때, 예외가 발생한 위치에서 예외 객체를 생성하여 직접 처리하지 않고, 예외를 호출한 곳으로 전달합니다. 이때, 호출한 곳이 메서드 시그니처에 throws AgeInputException을 명시한 경우에는 해당 예외를 호출한 메서드에 전달하게 됩니다.
- 예외 클래스를 정의할 때 해당 예외 상황에 필요한 문자열을 Exception 클래스의 생성자에 전달하면 된다.
- throw문이 존재하는 위치가 예외상황이 발생했음을 알리는 지점이 된다.
따라서 이 부분을 try~catch문으로 감싸서 예외상황을 처리할 수도 있는데, 이 예제에서는 throw문이 존재하는 부분을 try~catch문으로 감싸지 않고 있다.
이러한 경우 예외의 처리는, throw문이 존재하는 메서드(readAge 메서드)를 "호출한 영역"으로 넘겨지게 된다.
그래서 이 예제에서 main 메소드의 readAge 호출문장을 try~catch문으로 감싸는 것이다.
따라서 예외상황을 알리기 위해 생성된 AgeInputException 인스턴스의 참조 값은, main 메서드에 존재하는 catch 영역으로 전달되어 예외상황이 처리된다.
1) 예외 상황이 메서드 내에서 처리되지 않으면, 메서드를 호출한 영역으로 예외의 처리가 넘어감을 명시해야 한다.
- readAge 메서드의 선언부를 보면 throws AgeInputException이 있다.
- 이는 readAge 메서드 내에서는 AgeInputException에 대한 예외상황을 처리하지 않으니, 이 메서드를 호출하는 영역에서 AgeInputException에 대한 처리도 대비해야한다는 선언이다.
- 이렇듯 throw에 의해 생성된 예외상황이 메서드 내에서 처리되지 않는다면, 메서드를 호출한 영역으로 넘어감을 반드시 명시해야 한다.
2) throw에 의해 생성된 예외상황은 반드시 try~catch문에 의해 처리되거나 throws에 의해서 넘겨져야 한다.
- 즉 main 메서드 내에서도 AgeInputException의 예외상황을 처리하지 않는다면, main 메서드 역시 throws를 이용해서 이 예외상황을 처리를 넘겨야 한다.
이와 관련하여 다음 예제.
ㅁ 다음 예제
- ThrowsFromMain.java
import java.util.Scanner;
class AgeInputException extends Exception {
public AgeInputException(){
super("유효하지 않은 나이가 입력되었습니다.");
}
}
class ThrowsFromMain {
public static void main(String[] args) throws AgeInputException
{
System.out.print("나이를 입력하세요 : ");
int age = readAge();
System.out.println("당신은 " + age + "세입니다.");
}
public static int readAge() throws AgeInputException {
Scanner keyboard = new Scanner(System.in);
int age = keyboard.nextInt();
if(age<0)
{
AgeInputException excpt = new AgeInputException();
throw excpt;
}
return age;
}
}
[실행 결과]
나이를 입력하세요 : -2
Exception in thread "main" AgeInputException : 유효하지 않은 나이가 입력되었습니다.
at ThrowsFromMain.readAge(ThrowsFromMain.java : 26)
at ThrowsFromMain.main(ThrowsFromMain.java : 16)
- 이 예제의 throws excpt에서 발생한 예외상황은, main 메서드의 int age = readAge();를 거쳐서, main 메서드를 호출한 영역으로 넘거가게 된다.
그런데 main 메서드는 가상머신이 호출하는 메서드이다.
즉 예외상황의 처리는 가상머신에게까지 넘어가게 되고 ,결과적으로 예외상황의 처리는 가상머신에 의해서 이뤄진다.
- 가상머신의 예외처리 방식은
1) getMessage 메서드를 호출("유효하지 않은 나이가 입력되었습니다." 문자열 출력)
2) 예외상황이 발생해서 전달되는 과정을 출력 (26행에서 예외상황 발생, 16행으로 전달되어서 예외상황이 처리되었다.)
3) 프로그램 종료
ㅁ 예제3
- 방금 전 예제의 실행결과에서, 예외가 발생해서 전달되는 과정이 출력되게끔 하면, 프로그램에 존재하는 오류를 파악하는데 매우 도움이 된다.
방금 전은 main에서 또 JVM으로 넘긴 거지만, 그렇게 하지 않고도 이런 메세지를 출력할 수 있다.
Trowable 클래스에 정의되어 있는 printStackTrace 메서드가 이러한 유형의 메시지를 출력하기 때문이다.
import java.util.Scanner;
class AgeInputException extends Exception
{
public AgeInputException() {
super("유효하지 않은 나이가 입력되었습니다.");
}
}
class NameLengthException extends Exception
{
String wrongName;
public NameLengthException(String name) {
super("잘못된 이름이 삽입되었습니다.");
wrongName = name;
}
public void showWrongName() {
System.out.println("잘못 입력된 이름 : " + wrongName);
}
}
class PersonalInfo
{
String name;
int age;
public PersonalInfo(String name, int age) {
this.name = name;
this.age = age;
}
public void showPersonalInfo() {
System.out.println("이름 : " + name);
System.out.println("나이 : " + age);
}
}
class PrintStackTrace
{
public static Scanner keyboard = new Scanner(System.in);
public static void main(String[] args)
{
try
{
PersonalInfo readInfo = readPersonalInfo();
readInfo.showPersonalInfo();
}
catch(AgeInputException e) {
e.printStackTrace();
}
catch(NameLengthException e) {
e.showWrongName();
e.printStackTrace();
}
}
public static PersonalInfo readPersonalInfo()
throws AgeInputException, NameLengthException
{
String name = readName();
int age = readAge();
PersonalInfo pInfo = new PersonalInfo(name, age);
return pInfo;
}
public static String readName() throws NameLengthException {
System.out.print("이름 입력 : ");
String name = keyboard.nextLine();
if(name.length()<2)
throw new NameLengthException(name);
return name;
}
public static int readAge() throws AgeInputException {
System.out.print("나이 입력 : ");
int age = keyboard.nextInt();
if(age<0)
throw new AgeInputException();
return age;
}
}
[실행 결과]
이름 입력 : 이수진
나이 입력 : -25
AgeInputException: 유효하지 않은 나이가 입력되었습니다.
at PrintStackTrace.readAge(PrintStackTrace.java:89)
at PrintStackTrace.readPersonalInfo(PrintStackTrace.java:70)
at PrintStackTrace.main(PrintStackTrace.java:51)
- 예외는 89행에서 발생해서 70행, 그리고 51행으로 전달되어서 처리되었다.
그리고 printStackTrace 메서드가 호출되면, getMessage 메서드 호출 시 반환되는 문자열도 더불어 출력된다는 사실도 알 수 있다.
ㅁ 예외 클래스의 계층도와 Error 클래스
- Throwable 클래스를 상속하는 예외 클래스는 Exception과 Error 두 가지이다.
- Error 클래스는 단순히 예외라고 하기에는 심각한 오류의 상황을 표현하기 위해 정의된 클래스이다.
따라서 이 클래스를 상속하여 정의된 클래스는 프로그램의 실행을 멈춰야 할 정도의 매우 심각한 오류 상황을 표현한다.
- Error를 상속받는 대표적인 클래스에는 VirtualMachineError가 있다.
가상머신에 문제가 생겨서 더 이상 제대로 동작할 수 없는 상황을 알리기 위해서 정의된 클래스다.
이러한 상황은 try~catch문으로 해결할 사안이 아니다.
- 따라서 Error를 상속하는 클래스의 오류상황이 발생하면 그냥 프로그램이 종료되도록 나두는 것이 상책이다.
프로그램이 종료된 뒤 소스코드를 수정하는 등의 방식으로 원인을 해결해야 한다.
- VirtualMachineError를 상속하는 클래스 중에 OutOfMemoryError 클래스가 있는데, 메모리 공간이 부족한 상황을 표현하는 예외 클래스이다. 이런 오류가 발생하면 메모리가 효율적으로(또는 적절히) 사용되도록 소스코드 자체를 변경해야 한다.
- 이렇듯 Error와 관련 있는 오류상황은 소스코드의 변경을 통해서 해결해야 하는 경우가 대부분이다.
ㅁ Exception과 API 문서
- Error를 상속하는 클래스를 제외하고 보면, Exception은 모든 예외 클래스의 상위 클래스이다.
그래서 앞서 예외 클래스를 정의하는 과정에서 Exception 클래스를 상속했고, throw의 대상이 될 수 있었다.
ㅁ 메소드를 볼 때 throws 절을 참조하세요.
- 임의의 메소드 내에서 Exception을 상속하는 클래스의 예외상황이 발생 가능하다면,
(1) 해당 메소드는 throws를 이용해서 메소드를 호출한 영역으로 예외가 전달되도록 정의하거나,
(2) try~catch문을 이용해서 메소드 내에서 예외를 처리하도록 정의해야 한다.
- 이는 다른 메소드의 호출로 인해서 예외를 전달받는 메소드의 정의에서도 마찬가지이다.
(예를 들어, 메소드 A에서 메소드 B를 호출하고, 메소드 B에서는 예외가 발생할 수 있는 상황이라면, 메소드 A에서는 이 예외를 처리할 준비가 필요합니다. 이 처리 방법으로는 throws 절을 통해 예외를 상위 메소드로 넘기거나, try-catch 문을 사용해 예외를 직접 처리하는 방법이 있습니다.)
- Object는 클래스는 모든 클래스의 최상위 클래스이므로, Object 클래스의 clone() 이라는 메소드는 어느 인스턴스에건 호출이 가능하다.
- protected Object clone() throws CloneNotSupportedException
그런데 clone()이라는 메소드는 예외상황을 자신을 호출한 곳으로 던지고 있으므로, clone()을 호출한 영역에서 try~catch문을 사용하든지 또 throws를 쓰든지 해서 처리를 해야 컴파일 에러가 발생하지 않는다.
public void simpleMethod(int n) throws CloneNotSupportedException {
Myclass my = new MyClass();
try {
my.clone();
}
catch(CloneNotSupportedException e) { ... }
}
- 따라서 앞으로는 메소드의 호출문을 구성할 때, API 문서를 참조하여 해당 메소드가 예외를 전달하는지 확인하고, 예외를 전달한다면 상황에 맞게 try~catch문을 삽입하거나 throws절을 추가해야 한다.
ㅁ 처리하지 않아도 되는 RuntimeException의 하위 클래스
- RuntimeException을 상속하는 예외 클래스는 Error를 상속하는 예외 클래스와 마찬가지로 try~catch문, 또는 throws절을 이용한 예외처리를 필요로 하지 않는다.
- 그러나 Error를 상속하는 예외 클래스처럼 치명적인 상황을 표현하는 것은 아니므로, 예외발생 이후에도 프로그램의 실행을 이어가기 위해서 try~catch문으로 해당 예외를 처리하고도 한다.
- RuntimeException의 하위 클래스는 ArrayIndexOutOfBoundsException, ClassCastException, NegativeArraySizeException, NullPointerException이 있다.
ㅁ 예외 클래스는 Exception 클래스를 상속해서 정의하며, 키워드 throw를 이용해서 예외를 발생시킨다.
ㅁ 반복문에서 예외가 발생한 경우 사용자가 다시 이전으로 돌아가 선택을 하게끔하려면
- System.out.println("예외 발생");
i -= 1;
continue;
ㅁ 예외가 발생한 경우 무조건 맨 처음으로 되돌리고 싶다면
- main에서 처음부터 while(true)로 시작을 하고, while문 안에 try~catch문을 사용해서, try에서 예외가 발생하면 catch에서 예외가 발생했다고 출력. 이러면 자동으로 while문이 한번 끝나서 다시 처음부터 반복함.
'난 정말 JAVA를 공부한적이 없다구요' 카테고리의 다른 글
23. 쓰레드(Thread)와 동기화 (0) | 2024.07.03 |
---|---|
22. 컬렉션 프레임워크(Collection Framework) (0) | 2024.06.27 |
21. 제네릭(Generics) (0) | 2024.06.25 |
20. 자바의 다양한 기본 클래스 (0) | 2024.06.21 |
19. 자바의 메모리 모델과 Object 클래스 (0) | 2024.06.20 |