자바 자바학원 java java학원 scjp scjp학원 자바자격증 java자격증 scjp자격증 jsp ejb 자바프로그래밍 웹프로그래밍
[자바/JAVA/SCJP]예외 처리(Exception Control)
프로그램을 작성하여 실행하다 보면 프로그램이 다운될 때가 있다. 어떤 오류(Error)가 생겼기 때문인데 자바는 이런 오류가 발생하더라도 프로그램을 종료시키지 않고 유연하게 대처하는 방법을 제공한다. 하지만 모든 오류를 잡을 수 있는 것은 아니다. 자바는 잡을 수 있는 오류를 예외(Exception)라고 부른다.
예외가 발생하는 대부분의 이유는 프로그램을 잘못 작성했기 때문이지만 뜻하지 않게 발생할 수도 있다. 특히 입출력할 때 뜻하지 않은 예외가 많이 발생한다. 하지만 이런 예외도 마음껏 다룰 수 있다.
다음 코드를 컴파일하고 실행해보자.
public class Excep1{
public static void main(String[] args){
System.out.println("프로그램 시작");
int a=1,b=0;
int c=a/b; // x1
System.out.println(c);
System.out.println("프로그램 종료");
}
} |
컴파일은 성공적으로 수행될 것이다. 하지만 실행할 때 다음과 같은 에러 메시지를 출력하고 프로그램은 종료할 것이다.
프로그램 시작
java.lang.ArithmeticException: / by zero
at Excep1.main(Excep1.java:5)
Exception in thread "main" |
x1행에서 0으로 나누었기 때문에 오류가 발생하여 프로그램이 비정상적으로 종료한 것이다. 자바는 이런 오류를 예외라고 부르고 객체로 취급한다. 예외가 발생했다는 것은 예외 객체가 생겼다는 말과 같다.
x1행에서 발생한 예외 객체는 ArithmeticException 클래스의 객체로 수학적 예외가 발생하였을 때 생성되는 객체이다.
try~catch
try~catch문을 사용하면 예외가 발생하더라도 프로그램을 종료시키지 않고 계속해서 프로그램을 실행할 수 있다. 다음 그림은 try~catch문의 기본 사용법을 나타낸다.
try{
// x1, 예외 객체가 발생하는 부분
}
catch(ArithmeticException ae){ // x2, 예외 잡기
// x3, 여기서 처리
} |
x1행을 시도(try)해보고 예외가 발생하면 x2행에서 이 예외 객체를 잡아서(catch) x3행에서 적절한 처리를 한다. 예외 객체를 잡는다는 것은 ae가 예외 객체를 참조한다는 것이다. ae가 예외 객체를 참조하기 때문에 x3행에서 예외 객체에 대한 어떤 처리를 할 수 있다. 그런데 ae가 잡을 수 있는 예외는 ArithmeticException형 객체이다. 따라는 다른 종류의 예외가 발생하면 x2행에서 잡지 못한다.
public class Excep2{
public static void main(String[] args){
System.out.println("프로그램 시작");
int a=1,b=0;
try{
int c=a/b; // x1, 여기서 예외 객체 생김
System.out.println(c); // x2
}catch(ArithmeticException ae){ // x3
System.out.println("0으로 나누면 안되지~잉"); // x4
}
System.out.println("프로그램 종료"); // x5
}
}
프로그램 시작
0으로 나누면 안되지~잉
프로그램 종료
x1행에서 예외(ArithmeticException)가 발생한다. x3행에서 이 예외를 잡아서 x4행에서 적절한 예외 처리를 수행한다. x5행을 실행하고 프로그램이 정상적으로 종료된다. x1행에서 예외가 발생하였으므로 x2행은 실행되지 않고 catch문으로 넘어 간다는 것에 주의하자. x1행에서 ArithmeticException 예외가 아닌 다른 예외가 발생하면 x3행에서 이 예외를 처리하지 않는다.
NullPointerException은 레퍼런스가 null을 참조할 때 발생하는 예외이다. null을 참조한다는 것은 레퍼런스가 어떤 객체도 참조하지 않는다는 것을 독자는 이미 알고 있을 것이다. 참조하는 객체가 없기 때문에 멤버도 역시 존재하지 않는다.
Excep3 ob=null;
ob.hi(); // NullPointerException 발생 |
다음 예제는 NullPointerException이 발생했을 때 예외 처리를 하는 예제이다.
public class Excep3{
void hi(){
System.out.println("하이여~~");
}
public static void main(String[] args){
Excep3 ob=null;
try{
ob.hi(); // NullPointerException 발생
}catch(NullPointerException ne){
ob=new Excep3(); // 객체 할당
}
ob.hi(); // 여기서 출력
}
}
하이여~~
여러 종류의 예외를 처리하고자 할 경우에는 catch를 중복해서 사용한다.
try{①}
catch(ArithmeticException e){②}
catch(NullPointerException e){③}
catch(ArrayIndexOutOfBoundsException e){④}
finally{⑤} |
finally는 try~catch문 맨 아래쪽에 작성하고, 예외 발생 유무와 상관없이 무조건 실행된다. finally는 생략할 수도 있으며 무조건 실행해야할 명령이 있을 때 사용하면 된다. 위 코드의 실행 순서는 다음과 같다.
①에서 ArithmeticException가 발생하면 ②, ⑤를 실행한다.
①에서 NullPointerException이 발생하면 ③, ⑤를 실행한다.
①에서 ArrayIndexOutOfBoundsException이 발생하면 ④, ⑤를 실행한다.
알아두기 |
|
ArrayIndexOutOfBoundsException
배열(Array)의 원소 번호(index)가 범위(bounds)를 넘었을 때 발생하는 예외이다.
int[] a = new int[5]; // a[0], a[1], a[2], a[3], a[4]
a[10] = 200; // a[10]은 존재하지 않음, 예외 발생 |
public class Excep4{
public static void main(String[] args){
int[] a=new int[5];
try{
a[10]=200; // x1, ArrayIndexOutOfBoundsException
}catch(ArithmeticException e){
System.out.println(e);
}catch(NullPointerException e){
System.out.println(e);
}catch(ArrayIndexOutOfBoundsException e){ // x2
System.out.println(e); // x3
}finally{
System.out.println("무조건 실행"); // x4
}
}
}
java.lang.ArrayIndexOutOfBoundsException
무조건 실행
x1행에서 ArrayIndexOutOfBoundsException이 발생한다.
x2행에서 ArrayIndexOutOfBoundsException을 잡고 x3행을 실행한다.
x4행은 무조건 실행된다.
다음 소스를 컴파일 하면 어떤 예외가 발생하는가?
public class Alone11_1{
public static void main(String[] args){
int[] a;
a[10]=100;
}
}
Exception의 종류
앞에서 다룬 예외 말고도 많은 예외가 존재하고 상속 관계가 있다. 예외를 다루는 클래스들의 최상위 클래스는 Throwable이고 이를 상속하는 클래스는 각각 Exception과 Error이다. Error는 메모리가 부족하다든지 하는 치명적인 에러인데 처리하고 싶어도 별 뾰족한 수가 없으므로 다루지 않는다. 반면에 Exception은 try~catch로 잡을 수 있는 작은 에러이다.
[그림 11-1] Exception 계층도
Throwable은 '던질 수 있는'의 뜻인데 왜 그런 이름을 가지게 되었는지는 나중에 알게 될 것이다.
프로그램을 짤 때 자주 발생되는 예외는 RuntimeException이고 이 예외가 발생하는 대부분의 이유는 코딩을 잘못했기 때문이다. 앞으로 프로그램을 짜다가 이 예외가 발생하면 해당 코드를 올바르게 수정하자. 하지만 때에 따라 try~catch문을 사용해야만 하는 경우도 있다. RuntimeException은 Exception을 상속한다.
RuntimeException의 종류 |
설명 |
ArithmeticException |
수학적 예외, 예) 0으로 나눔 |
NullPointerException |
레퍼런스가 null을 참조할 때 발생 |
ArrayIndexOutOfBoundsException |
배열의 범위를 초과할 때 |
NegativeArraySizeException |
배열의 크기가 음(-)일 때 |
ArrayStoreException |
배열에 대입하는 값이 올바르지 않을 때 |
IllegalArgumentException |
매개 변수에 잘못된 값이 대입될 때 |
SecurityException |
보안상의 예외 |
[표 11-1] RuntimeException의 종류
부모 레퍼런스는 모든 자식 객체를 참조할 수 있다. 따라서 모든 예외 객체의 부모인 Exception 레퍼런스는 모든 예외 객체를 참조할 수 있다. 같은 말로 Exception 레퍼런스는 모든 예외를 잡을 수 있다.
public class Excep5{
public static void main(String[] args){
try{
int[] a=new int[-1]; // NegativeArraySizeException 발생
}catch(Exception e){ // e는 모든 예외를 잡을 수 있다.
System.out.println(e);
}
}
}
java.lang.NegativeArraySizeException
예외를 던지는 메소드
어떤 메소드는 호출했을 때 예외를 발생시킬 수 있는데, 그런 메소드를 예외를 던지는 메소드라고 부른다. 다음 코드에서 go가 예외를 던지는 메소드이다.
static void go() throws NegativeArraySizeException{
int[] a=new int[-1]; // x1
}
// 'throws'는 말 그대로 '던지다'의 의미이다. |
예외를 try~catch로 처리할 수도 있지만 위와 같이 'throws'를 사용하여 예외를 처리할 수도 있다. go을 호출하여 실행하면 x1행에서 예외가 발생하는데 go를 호출한 곳으로 발생한 예외 객체를 던진다. 예제를 보면 쉽게 이해할 수 있을 것이다.
public class Excep6{
static void go() throws NegativeArraySizeException{
int[] a=new int[-1]; // x1
}
public static void main(String[] args){
try{
go(); // x2, 여기로 예외를 던진다.
}catch(NegativeArraySizeException e){ // x3
System.out.println("go가 예외를 던진다.");
}
}
}
go가 예외를 던진다.
x2행의 go를 호출하면 x1행을 실행하다가 예외가 발생한다. go는 예외를 던지는 메소드이므로 자기를 호출한 곳, 즉 x2행으로 예외 객체를 던진다. 진짜 예외는 x1행에서 발생하였지만 x2행으로 던졌기 때문에 x2행에서 예외가 발생하는 것이다. 증거로 x3행에서 예외가 잡히고 있다.
날라 온 예외 객체를 또 던질 수 있다. 얼마든지 던질 수 있다. 나중에 잘 잡으면 되니까.
public class Excep7{
static void go() throws NegativeArraySizeException{
int[] a=new int[-1]; // x1
}
static void hi() throws NegativeArraySizeException{
go(); // x2
}
public static void main(String[] args){
try{
hi(); // x3, 여기로 예외 객체가 던져짐
}catch(NegativeArraySizeException e){
System.out.println("멀리서 날라 왔네..");
}
}
}
멀리서 날라 왔네..
x3행에서 hi를 호출하면 x2행에서 go를 호출하고 x1행에서 예외가 발생한다. 발생한 예외 객체를 x2행으로 던지고, x2행에 날라 온 예외 객체를 x3행으로 던진다. go나 hi는 예외를 던지는 메소드이기 때문이다.
예외를 던지는 메소드라고 해서 무조건 예외가 발생하는 것은 아니다. 예외를 던지는 메소드란 예외를 던질 가능성이 있는 메소드를 말하는 것이다. 예제를 보자.
public class Excep8{
static void go(int size) throws NegativeArraySizeException{
int[] a=new int[size]; // size가 음수일 때만 예외 발생
System.out.println(a.length);
}
public static void main(String[] args){
try{
go(10); // x1, 예외가 발생하지 않음
}catch(NegativeArraySizeException e){
System.out.println("배열의 크기는 양수");
}
}
}
10
출력 결과를 보면 예외가 발생하지 않았다. x1행에서 10대신에 음수를 대입하면 예외가 발생할 것이다. 실제로 해보길 바란다.
예외를 던지는 메소드는 반드시 예외 처리를 해야한다. 아니면 컴파일 에러가 발생한다. 다음 예제를 컴파일 해보자.
public class Excep9{
public static void main(String[] args){
int a=0;
System.out.print("문자를 누르세요>> ");
// System.in.read()는 IOException을 던지는 메소드이다.
a=System.in.read(); // x1
System.out.println("누르신 문자는 "+ (char)a+"입니다.");
}
}
C:\...\Excep9.java:5: unreported exception java.io.IOException;
must be caught or declared to be thrown
a=System.in.read();
^
1 error
에러 메시지는 x1행에서 java.io.IOExcepion을 잡거나 던져야한다는 내용이다. x1행의 read()가 IOException을 던지는 메소드이므로 아래와 같이 예외 처리를 해야만 컴파일이 정상적으로 수행된다.
import java.io.IOException; // IOException을 import한다.
public class Excep10{
public static void main(String[] args){
int a=0;
System.out.print("문자를 누르세요>> ");
try{
a=System.in.read(); // x1
}catch(IOException e){
System.out.println("오류~~~~");
}
System.out.println("누르신 문자는 "+ (char)a+"입니다.");
}
}
문자를 누르세요>> A
누르신 문자는 A입니다.
read 메소드는 예외를 던지는 메소드로 정의되어 있다.
public abstract int read() throws IOException; |
따라서 예외 처리를 반드시 해야한다. 그러나 예제의 실행 결과처럼 예외를 던지지 않을 수도 있다. 참고로 read 메소드는 표준 입력 스트림(키보드)으로부터 1바이트를 읽어오는 메소드이다. 나중에 입출력 부분에서 자세히 살펴보자.
알아두기 |
|
RuntimeException을 던지는 메소드
RuntimeException은 예외 처리를 하지 않아도 컴파일은 수행된다. |
키보드로부터 한 문자를 입력받아 문자에 해당하는 ASCII를 출력하는 프로그램을 만들어보자.
※ System.in.read()는 1byte를 읽어서 int로 반환한다.
문자: A ??
ASCII: 65
문자: b ??
ASCII: 98
추천학원
자바 자바학원 java java학원 scjp scjp학원 자바자격증 java자격증 scjp자격증 jsp ejb 자바프로그래밍 웹프로그래밍