프로그램이 실행 중 어떤 원인에 의해서 오작동을 하거나 비정상적으로 종료되는 경우가 있다. 이러한 결과를 초래하는 원인을 프로그램 에러 또는 오류라고 한다.
컴파일 에러 : 컴파일 시에 발생하는 에러
런타임 에러 : 실행 시에 발생하는 에러
논리적 에러 : 실행은 되지만, 의도와 다르게 동작하는 것
에러가 발생하면, 프로그램의 비정상적인 종료를 막을 길이 없지만, 예외는 발생하더라도 프로그래머가 이에 대한 적절한 코드를 미래 작성해 놓음으로써 프로그램의 비정상적인 종료를 막을 수 있다.
에러(error) : 프로그램 코드에 의해서 수습될 수 없는 심각한 오류
예외(exception) : 프로그램 코드에 의해서 수습될 수 있는 다소 미약한 오류
모든 클래스의 조상은 Object클래스이므로 Exception과 Error클래스 역시 Object클래스의 자손들이다.
RuntimeException클래스와 그 자손 클래스들을 'RuntimeException클래스들'이라 하고, RuntimeException클래스들을 제외한 나머지 클래스들을 'Exception클래스들'이라 하겠다.
RuntimeException클래스들은 주로 프로그래머의 실수에 의해서 발생될 수 있는 예외들로 자바의 프로그래밍 요소들과 관계가 깊다. 예를 들면, 배열의 범위를 벗어난다던가(ArrayIndexOutOfBoundsException), 값이 null인 참조변수의 멤버를 호출하려 했다던가(NullPointerException), 클래스간의 형변환을 잘못했다던가(ClassCastException). 정수를 0으로 나누려고(ArithmeticException)하는 경우에 발생한다
Exception클래스들은 주로 외부의 영향으로 발생할 수 있는 것들로서, 프로그램의 사용자들의 동작에 의해서 발생하는 경우가 많다. 예를 들면, 존재하지 않는 파일의 이름을 입력했다던가(FileNotFoundException), 실수로 클래스의 이름을 잘못 적었다던가(ClassNotFoundException), 또는 입력한 데이터 형식이 잘못된(DataFormatException) 경우에 발생한다
Exception클래스들 : 사용자의 실수와 같은 외적인 요인에 의해 발생하는 예외
RuntimeException클래스들 : 프로그래머의 실수로 발생하는 예외
예외처리(Exception handling)란, 프로그램 실행 시 발생할 수 있는 예기치 못한 예외 발생에 대비한 코드를 작성하는 것이며, 예외처리의 목적은 예외의 발생으로 인한 실행 중인 프로그램의 갑작스런 비정상 종료를 막고, 정상적인 실행상태를 유지할 수 있도록 하는 것이다.
예외처리(exception handling)의
정의- 프로그램 실행 시 발생할 수 있는 예외에 대비한 코드를 작성하는 것
목적- 프로그램의 비정상 종료를 막고, 정상적인 실행상태를 유지하는 것
예외를 처리하기 위해서는 try-catch문을 사용하며, 그 구조는 다음과 같다
try{
//예외가 발생할 가능성이 있는 문장들을 넣는다
}catch(Exception1 e1){
//Exception1이 발생했을 경우, 이를 처리하기 위한 문장을 적는다
}catch(Exception2 e2){
//Exception2이 발생했을 경우, 이를 처리하기 위한 문장을 적는다
}catch(ExceptionN eN){
//ExceptionN이 발생했을 경우, 이를 처리하기 위한 문장을 적는다
}
try블럭 내에서 예외가 발생한 경우,
1. 발생한 예외와 일치하는 catch블럭이 있는지 확인한다
2. 일치하는 catch블럭을 찾게 되면, 그 catch블럭 내의 문장들을 수행하고 전체 try-catch문을 빠져나가서 그 다음 문장을 계속해서 수행한다. 만일 일치하는 catch블럭을 찾지 못하면, 예외는 처리되지 못한다
try블럭 내에서 예외가 발생하지 않은 경우,
1. catch블럭을 거치지 않고 전체 try-catch문을 빠져나가서 수행을 계속한다
class ExceptionEx{
public static void main(String[] args) {
System.out.println(1);
System.out.println(2);
try{
System.out.println(3);
System.out.println(0/0);
System.out.println(4);
}catch(ArithmeticException ae){
System.out.println(5);
}
System.out.println(6);
}
}
catch블럭은 괄호()와 블럭{} 두 부분으로 나눠져 있는데, 괄호()내에는 처리하고자 하는 예외와 같은 타입의 참조변수 하나를 선언해야한다
printStackTrace( ) : 예외발생 당시의 호출스택(Call Stack)에 있었던 메서드의 정보와 예외 메시지를 화면에 출력한다
getMessage( ) : 발생한 예외클래스의 인스턴스에 저장된 메시지를 얻을 수 있다
public class ExceptionEx {
public static void main(String[] args) {
System.out.println(1);
System.out.println(2);
try{
System.out.println(3);
System.out.println(0/0);//예외발생
System.out.println(4);//실행 X
}catch(ArithmeticException e){
e.printStackTrace();
System.out.println("예외메시지 : "+e.getMessage());
}
System.out.println(6);
}
}
ArithmeticException인스턴스의 printStackTrace( )를 사용해서, 호출 스택(call stack)에 대한 정보와 예외 메시지를 출력했다.
try{
...
}catch(ExceptionA e){
e.printStackTrace();
}catch(ExceptionB e2){
e.printStackTrace();
}
try{
...
}catch(Exception A | ExceptionB e){
e.printStackTrace();
}
catch블럭을 '|'기호를 이용해서, 하나의 catch블럭으로 합칠 수 있게 되었으며, 이를 '멀티 catch블럭'이라 한다.
키워드 throw를 사용해서 프로그래머가 고의로 예외를 발생시킬 수 있다.
- 먼저, 연산자 new를 이용해서 발생시키려는 예외 클래스의 객체를 만든 다음
- Exception e = new Exception("고의로 발생시킴");
- 키워드 throw를 이용해서 예외를 발생시킨다
- throw e;
public class ExceptionEx10 {
public static void main(String[] args) {
try{
Exception e = new Exception("고의로 발생시킴");
throw e;
}catch(Exception e){
System.out.println("에러 메시지 : "+e.getMessage());
e.printStackTrace();
}
System.out.println("프로그램이 정상 종료당함");
}
}
public class ExceptionEx11 {
public static void main(String[] args) {
throw new RuntimeException();
}
}
RuntimeException이 발생하여 비정상적으로 종료될 것이다
메서드에 예외를 선언하려면, 메서드의 선언부에 키워드 throws를 사용해서 메서드 내에서 발생할 수 있는 예외를 적어주기만 하면 된다.
void method() throws Exception1, Exception2, Exception3, ..., ExceptionN{
//메서드의 내용
}
메서드 선언부에 예외를 선언함으로써 메서드를 사용하려는 사람이 메서드의 선언부를 보았을 때, 이 메서드를 사용하기 위해서는 어떠한 예외들이 처리되어져야 하는지 쉽게 알 수 있다.
메서드의 예외를 선언할 때 일반적으로 RuntimeException클래스들은 적지 않는다.
public class ExceptionEx12 {
public static void main(String[] args) throws Exception {
method1();
}
static void method1() throws Exception{
method2();
}
static void method2() throws Exception{
throw new Exception();
}
}
finally블럭은 예외의 발생여부에 상관없이 실행되어야할 코드를 포함시킬 목적으로 사용된다
try{
//예외가 발생할 가능성이 있는 문장들을 넣는다
}catch(Exception1 e1){
//예외처리를 위한 문장을 적는다
}finally {
//예외의 발생여부에 관계없이 항상 수행되어야하는 문장들을 넣는다
//finally블럭은 try-catch문의 맨 마지막에 위치해야한다.
}
예외가 발생한 경우에는 'try->catch->finally'의 순으로 실행되고, 예외가 발생하지 않은 경우에는 'try->finally'의 순으로 실행된다
public class FinallyTest {
public static void main(String[] args) {
try {
startInstall();//프로그램 설치에 필요한 준비를 한다
copyFiles();//파일들을 복사한다
}catch(Exception e){
e.printStackTrace();
}finally{
deleteTempFiles();//프로그램 설치에 사용된 임시파일들을 삭제한다
}
}
static void startInstall(){
//코드를 작성한다
}
static void copyFiles(){
//파일들을 복사하는 코드를 적는다
}
static void deleteTempFiles(){
//임시파일들을 삭제하는 코드를 적는다
}
}
try-with-resources문이라는 try-catch문의 변형이 새로 추가되었다.
try{
fis = new FileInputStream("score.dat");
dis = new DataInputStream(fis);
...
}catch(IOException ie){
ie.printStackTrace();
}finally{
dis.close();//작업 중 예외가 발생하더라도, dis가 닫히도록 finally블럭에 넣음
}
finally블럭 안에 close( )를 넣었지만, close( )가 예외를 발생시킬 수 있다.
try{
fis = new FileInputStream("score.dat");
dis = new DataInputStream(fis);
...
}catch(IOException ie){
ie.printStackTrace();
}finally{
try{
if(dis!=null){
dis.close();
}
}catch(IOException ie){
ie.printStackTrace();
}
}
try블럭과 finally블럭에서 모두 예외가 발생하면, try블럭의 예외는 무시된다는 것이다.
이러한 점을 개선하기 위해서 try-with-resources문이 추가된 것이다.
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-resource문의 괄호( )안에 객체를 생성하는 문장을 넣으면, 이 객체는 따로 close( )호출하지 않아도 try블럭을 벗어나는 순간 자동적으로 close( )가 호출된다.
보통 Exception클래스 또는 RuntimeException클래스로부터 상속받아 클래스를 만들지만, 필요에 따라서 알맞은 예외 클래스를 선택할 수 있다.
class MyException extends Exception{
MyException(String msg){
super(msg);
}
}
class MyException extends Exception{
private final int ERR_CODE;//생성자를 통해 초기화한다
MyException(String msg, int errCode){
super(msg);//조상인 Exception클래스의 생성자를 호출한다
ERR_CODE = errCode;
}
MyException(String msg){
this(msg,100);//ERR_CODE를 100(기본값)으로 초기화한다
}
public int getErrCode(){
return ERR_CODE;
}
}
MyException이 발생했을 때, catch블럭에서 getMessage( )와 getErrCode( )를 사용해서 에러코드와 메시지를 모두 얻을 수 있을 것이다
public class NewExceptionTest {
public static void main(String[] args) {
try{
startInstall();
copyFiles();
}catch(SpaceException e){
System.out.println("에러 메시지 : "+e.getMessage());
e.printStackTrace();
System.out.println("공간을 확보한 후에 다시 설치하시기 바랍니다.");
}catch(MemoryException me){
System.out.println("에러 메시지 : "+me.getMessage());
me.printStackTrace();
System.gc();//Garbage Collection을 수행하여 메모리를 늘려준다
System.out.println("다시 설치를 시도하세요.");
}finally {
deleteTempFiles();
}
}
static void startInstall() throws SpaceException, MemoryException{
if(!enoughSpace()){
throw new SpaceException("설치할 공간이 부족합니다");
}
if(!enoughMemory()){
throw new MemoryException("메모리가 부족합니다");
}
}
static void copyFiles(){/*코드작성한다*/}
static void deleteTempFiles(){/*코드 작성한다*/}
static boolean enoughSpace(){
//설치하는데 필요한 공간이 있는지 확인하는 코드를 작성한다
return false;
}
static boolean enoughMemory(){
//설치하는데 필요한 메모리공간이 있는지 확인하는 코드는 적는다
return true;
}
}
class SpaceException extends Exception{
SpaceException(String msg){
super(msg);
}
}
class MemoryException extends Exception{
MemoryException(String msg){
super(msg);
}
}
MemoryException과 SpaceException, 이 두 개의 사용자정의 예외 클래스를 새로 만들어서 사용했다.
한 메서드에서 발생할 수 있는 예외가 여럿인 경우, 몇 개는 try-catch문을 통해서 메서드 내에서 자체적으로 처리하고, 그 나머지는 선언부에 지정하여 호출한 메서에서 처리하도록 함으로써, 양쪽에서 나눠서 처리되도록 할 수 있다.
예외를 처리한 후에 인위적으로 다시 발생시키는 방법을 통해서 가능한데, 이것을 '예외 되던지기(exception re-throwing)'라고 한다.
public class ExceptionEx17 {
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;//다시 예외를 발생시킨다
}
}
}
예외 A가 예외 B를 발생시켰다면, A를 B의 '원인 예외(cause exception)'라고 한다.
try{
startInstall();//SpaceException 발생
copyFiles();
}catch(SpaceException e){
InstallException ie - new InstallException("설치중 예외발생");//예외 생성
ie.initCause(e);//InstallException의 원인 예외를 SpaceException으로 지정
throw ie;
}catch(MemoryException me){
...
}
먼저 InstallException을 생성한 후에, initCause( )로 SpaceException을 InstallException의 원인 예외로 등록한다. 그리고 throw로 이 예외를 던진다
initCause( )는 Exception클래스의 조상인 Throwable클래스에 정의되어 있기 때문에 모든 예외에서 사용가능하다
Throwable initCause(Throwable cause) : 지정한 예외를 원인 예외로 등록
Throwable getCause( ) : 원인 예외를 반환
static void startInstall() throws SpaceException, MemoryException{
if(!enoughSpace()){
throw new SpaceException("설치할 공간이 부족합니다.");
}
if(!enoughMemory()){
throw new MemoryException("메모리가 부족합니다.");
}
}
static void startInstall() throws SpaceException{
if(!enoughSpace()){
throw new SpaceException("설치할 공간이 부족합니다.");
}
if(!enoughMemory()){
throw new RuntimeException(new MemoryException("메모리가 부족합니다."));
}
}
RuntimeException(Throwable cause) : 원인 예외를 등록하는 생성자