[Java] 예외 처리

Geehyun(장지현)·2024년 2월 8일
0

Java

목록 보기
10/12
post-thumbnail

예외(Exception)

  • 에러
    JVM 자체에서 발생하는 오류로 메모리 풀, 쓰레드가 죽었을 때개발자가 해결할 수 없는 오류를 말합니다.
  • 예외
    연산 오류, 숫자 포맷 오류 등과 같이 상황에 따라 개발자가 해결할 수 있는 오류를 말합니다.
  • 예외처리
    이러한 예외에 대해 오류 자체를 해결할 수는 없으나 예외 상황이 발생하지 않도록 차선책을 제시함으로써 오류를 피하는 과정을 예외처리 라고 합니다.

💡 예외와 에러 클래스 상속구도
예외(Exception)과 에러(Error) 모두 Throwble 클래스를 상속받고 있습니다.
Error와 Exception

예외 클래스

예외클래스

  • 일반예외(Checked Exception)
    Exception 클래스를 직접 상속받은 예외 클래스로, 컴파일 전 체크되어(문법오류 발생) 예외처리를 안할 경우 아예 실행자체가 안되는 예외 입니다.
  • 실행예외(Unchecked Exception
    Exception을 상속 받은 Runtime Exception 클래스를 상속받아 구현된 예외 클래스컴파일 때 예외 체크되지 않고(문법오류가 발생되지 않고) 실행 중 예외가 발생되어 프로그램이 강제 종료됩니다.

일반 예외

해당 실행예외의 경우 예외가 실제로 발생할 경우가 아닌, 해당 예외를 발생시킬 수 있는 문법을 사용할 경우 컴파일 전 체크하여 바로 문법 오류를 발생시켜 실행 자체를 막아버리는 예외들 입니다.

따라서 해당 예외를 발생시킬 수 있는 문법 사용시 반드시 그에 맞는 예외처리가 필요합니다.

  • ClassNotFoundException
    클래스를 동적으로 로딩하는 과정에서 해당 클래스가 존재하지 않을 경우 ClassNotFoundException이 발생할 수 있습니다.

    예시 : Class.forName("패키지명,클래스명")은 클래스를 동적으로 메모리에 로딩하는 메서드로 해당 메서드는 ClassNotFoundException을 발생시킬 수 있어 반드시 예외처리가 필요합니다.

  • InterruptedExcetpion
    자바에서 인터럽트란, 쓰레드를 종료하기 위한 메커니즘으로, 쓰레드간 인터럽트를 주고 받을 때 이를 블로킹할 수 있는 메서드를 사용할 경우 해당 예외가 발생합니다.

    예시 : Thread.sleep()메서드의 경우 일정 시간 동안 해당 쓰레드를 일시정지 상태로 만드는 메서드로, 일시정지 상태에서 인터럽트를 받을 경우 해당 일시정지 상태가 풀릴 때까지 대기해야하여 InterruptedException에 대한 예외처리가 필요합니다.

  • IOException
    자바 입출력 부분에서 발생하는 예외로 자바에서 입출력을 사용하게 될 경우 반드시 IOException에 대한 예외를 처리해줘야합니다.

  • FileNotFoundException
    IOException의 파생 클래스로 파일을 읽을 때 해당 경로에 파일이 없으면 예외를 발생시킵니다.
    따라서 파일을 읽어오는 메서드를 사용 시 해당 예외에 대한 예외처리가 반드시 필요합니다.

  • CloneNotSupporttedException
    Object 클래스의 clone() 메서드는 자신의 객체를 복사한 클록 객체를 생성하는 메서드로, 해당 메서드를 사용하려면 복사의 대상이 되는 클래스가 cloeable 인터페이스를 상속받아, 복사 가능 상태로 확인되어야 합니다.

    💡 cloneable 인터페이스 상속?
    cloneable 인터페이스는 내부에 아무런 추상 메서드도 포함하고 있지 않는, 그저 해당 인터페이스를 상속 받은 클래스가 '복제 가능 함'을 표시하는 마커(marker)의 기능만 수행하는 인터페이스 입니다.

실행 예외

실행예외는 컴파일 시 문법오류로 체크되지는 않으나 예외처리 없이 컴파일 시 실행 도중 실행예외가 발생되게 될 때 프로그램이 강제 종료되는 예외입니다.

  • ArithmeticException
    Arithmeti 이란, 산술 또는 연산이란 뜻으로 연산 자체가 불가할 때 발생하는 예외입니다.
    대표적인 ArithmeticException이 발생하는 경우는 분모가 0인 연산을 실행할 때 입니다.
  • ClassCastExcetpion
    불가능한 캐스팅을 실행할 때 발생되는 예외입니다.
    다운캐스팅이 불가능한 상황에서 다운캐스팅을 시도하는 경우 발생되고는 합니다.
  • ArrayIndexOutOfBoundsException
    배열의 인텍스를 잘못 사용했을 경우 발생되는 예외입니다.
    배열의 인덱스는 0 ~ 배열의 길이 -1 범위 내에서만 사용할 수 있으며, 이 범위 밖에서 사용시 예외가 발생됩니다.
  • NumberFormatException
    숫자가 아닌 것을 숫자로 변환하려고할 때 발생되는 예외입니다.
    문자열을 실수로 변환Double.parseDouble("문자열"), 정수로 변환Integer.parseInt("문자열")할 경우 변환하려고 하는 문자열의 형식이 숫자형식이 아닐 경우 자주 발생됩니다.
  • NullPointerException
    참조변수가 실제 객체를 가르키지 않는 상태를 null상태라고 하며 해당 상태일 때 해당 참조변수를 포함하는 메서드를 실행할 떄 발생되는 예외입니다.

이외에도 다양한 예외들이 있으며, 예외발생 시 메시지를 통해서 처리방법을 확인 후 처리해야합니다.

예외처리

예외를 처리할 때는 예외처리 구문, 예외 전가 등의 방법을 선택할 수 있습니다.
어떤 방식이든 예외 처리를 하게 될 경우 JVM은 예외에 대해 적절한 조치를 했든 안했든 예외가 처리되었다고 판단하여 프로그램을 강제 종료시키지 않습니다.

  • 예외처리(try-catch) : 예외처리가 필요한 코드에 대해 바로 예외처리 내용을 작성해주는 방식
  • 예외전가(throws) : 예외처리가 필요한 코드에 대해 예외처리 의무를 자신을 호출한 지점으로 전가하는 방식

먼저 예외처리 구문을 이용하는 방법을 보게습니다.

예외처리(try-catch)

예외처리 구문

// 사용법
try {
	예외가 발생할 가능성이 있는 코드;
} catch (예외클래스명 참조변수) {
	위 괄호 안에 작성한 예외가 발생할 경우 처리 내용;
} finally {
	예외가 발생하든 안하든 실행할 코드(생략가능);
}
// 사용예시
// 1. try-catch만 사용
try {
    System.out.println(3/0);
} catch(ArithmeticException e) {        
    System.out.println(e.getMessage());   //시스템이 보내는 메시지 확인
}

// 2. try-catch-finally 사용
try {
    System.out.println(3/0);
} catch(ArithmeticException e) {         // 작은 에러부터 확인해야 함.
    System.out.println(e.getMessage());   //시스템이 보내는 메시지 확인
} catch(Exception e) {
    System.out.println("숫자는 0으로 나눌 수 없습니다.");
    System.out.println(e.getMessage());   //시스템이 보내는 메시지 확인
}

// 3. 다중 catch문 사용 
try {
    System.out.println(3/0);
} catch(ArithmeticException e) {         // 작은 에러부터 확인해야 함.
    System.out.println(e.getMessage());   //시스템이 보내는 메시지 확인
} catch(Exception e) {
    System.out.println("숫자는 0으로 나눌 수 없습니다.");
    System.out.println(e.getMessage());   //시스템이 보내는 메시지 확인
} finally {
    System.out.println("프로그램 종료");
}
  • try
    예외가 발생할 수 있는 코드를 담아줍니다.
try {
	예외가 발생할 수 있는 코드;
    예외가 발생할 수 있는 코드2;
}
  • catch()
    catch()괄호 안에 예외 처리할 예외 클래스와 참조변수를 입력해줍니다.
    () 소괄호 안에 작성한 예외클래스에 대해서만 처리할 수 있습니다. (모든예외 한번에 처리 못함)
    {}바디 안에 해당 예외에 대해 처리할 내용을 작성합니다.
    ④ 여러 예외를 처리해줘야할 경우 다중 catch()를 사용하거나 () 안에 ||를 이용할 수 있습니다.
    ⑤ 다중 catch()를 사용할 경우 예외클래스의 범위가 작은 순으로 작성해줘야합니다.
// 기본 catch문
try {...} 
catch(예외클래스 참조변수) {
	예외 발생 시 처리할 코드;
    // 모든 예외에 대해 처리되는게 아니라 위 catch() 괄호 안에 작성된 예외에 대해서만 처리
}
// 다중 catch문
try {...} 
catch(예외클래스1 참조변수) {
	예외클래스1의 예외 발생 시 처리할 코드;
}
catch(예외클래스2 참조변수) {
	예외클래스2의 예외 발생 시 처리할 코드;
}
// catch문 ()내 || 사용
try {...} 
catch(예외클래스1 || 예외클래스2 참조변수) {
	예외 발생 시 처리할 코드;
}

💡 예외클래스가 작은...범위?...왜..?
catch문의 경우 위->아래로 읽어 실행하기 때문에 예외클래스의 범위가 큰 catch문을 위에 작성 후 그 보다 범위가 작은 예외 클래스를 바로 아래에 작성 시 예외클래스 범위가 큰 catch문에서 예외가 계속 처리되므로 범위가 작은 예외클래스의 catch문은 영원히 실행되지 않습니다.
따라서, catch문을 작성할 떄는 예외클래스의 범위에 따라 범위가 작은 것 부터 큰 순으로 순차적으로 작성해야합니다.

// catch절 예외클래스 잘못 작성한 예시
try {...}
catch(Exception e) {
	System.out.println("Exception 예외 처리 부분")
}
catch(IOException e) {
	 //위 Exception catch절에서 모든 예외가 잡히기 때문에 영원히 실행되지 않음
	System.out.println("IOException 예외 처리 부분")
}

// catch절 예외클래스 올바른 작성 예시
try {...}
catch(IOException e) {
	System.out.println("IOException 예외 처리 부분")
}
catch(Exception e) {
	System.out.println("Exception 예외 처리 부분")
}
  • finally
    예외가 발생하든 안하든 항상 실행줘야하는 코드를 작성합니다.
    필요 없을 시 생략할 수 있습니다.
// finally 작성
try {
    System.out.println(3/0);
} catch(Exception e) {
    System.out.println("숫자는 0으로 나눌 수 없습니다.");
} finally {
    System.out.println("프로그램 종료");
}
// finally 생략
try {
    System.out.println(3/0);
} catch(Exception e) {
    System.out.println("숫자는 0으로 나눌 수 없습니다.");
}

try with resources 구문

리소스 자동해제 구문을 말합니다. 기본적으로 리소스 해제가 필요한 경우 finally구문에서 수동으로 해당 리소스 해제 코드를 작성해주고는 합니다.

InputStreamReader is = null;
try {
	is = new InputStreamReader(System.in); //콘솔 입력
    System.out.println(is.read());
} catch(IOException e) {
	...;
} finally {
	is.close(); //수동으로 리소스 해제
}

위 처럼 리소스 반납이 필요한 경우 수동으로 리소스 해제 코드를 작성해야했으나, 인간은...실수를 할 수밖에 없는 존재이기에 리소스 해제를 누락하는 경우가 많아지고 코드의 간소화를 위해 리소스를 자동으로 해제할 수 있는 구문이 생겼습니다.

기존 try{}문과는 달리try(리소스 자동해제가 필요한코드;){...;}로 try 옆에 () 소괄호 안에 리소스 자동해제가 필요한 코드를 작성할 수 있습니다.

// 사용법
try(리소스 자동해제가 필요한 구문1; 리소스 자동해제가 필요한 구문2;) {
	...;
} catch(예외 클래스 참조변수) {
	...;
}
// 사용예시
try(InputStreamReader is = new InputStreamReader(System.in);) {
	System.out.println(is.read());
} catch(IOException e) {
	...;
}

💡 리소스 자동해제 구문은 모든 코드에 적용할 수 있을까?
리소스 자동해제 구문을 사용하기 위해서는 해당 클래스가 AutoCloseable인터페이스를 상속받아 close() 메서드를 구현해야만 합니다.

class A implements AutoCloseable {
	String resource;
  A(String resource) {
  	this.resource = resource;
  }
  @Override
  public void close() throws Exception {   //throws 구문은 예외전가 구문입니다.
  	resource = null;
  }
}

사용자가 생성한 클래스에 대해 리소스 자동해제 구문을 사용해야한다면 위의 예시 처럼 AutoCloseable인터페이스를 상속받아 close()메서드를 오버라이딩 하여 사용할 수 있습니다.

예외전가

try-catch문이 예외처리가 필요한 구문에 대해 그 자리에서 바로 예외처리를 해주는거라면,
예외전가는 예외처리를 직접 하지 않고 자신을 호출한 메서드로 그 의무를 넘기는 것입니다.

  • 예외전가를 하게 될 경우 해당 예외에 대한 예외처리는 해당 메서드를 호출한 지점이 됩니다.
  • 예외전가 시 던진 예외에 대해서만 예외전가가 됩니다.
    (=> 명시한 예외 외 다른 예외처리 의무는 호출 메서드가 아니라, 예외가 발생한 구문 자체에 있습니다.)
  • throws로는 예외를 여러개 전가할 수 있으며 그럴 경우 ,로 구분하여 사용하며 이 때도 작은예외, 큰 예외 순으로 작성해야합니다.
// 사용법
리턴타입 메서드명() throws 예외클래스명, 예외클래스명 {...}
// 사용예시
void abc() {
	try {
    	bcd();
    } catch(IOException e) {
    	...;
    }
}
void bcd() throws IOException {
	InputSteamReader is = new InputStreamReader(System.in);
    System.out.println(is.read());
}

💡 예외를 계속 전가만 한다면??
호출메서드로 계속 올라가 결국 main()메서드까지 올라가게 되고, main()메서드에서도 예외를 전가하게 되면 JVM이 예외를 직접 처리하게 됩니다.
JVM이 예외처리를 하게 될 경우 발생한 예외에 대한 정보를 콘솔에 출력하고, 프로그램을 강제종료하는 방식으로 처리합니다.

사용자 정의 예외 클래스

Java 내에서 제공하는 예외클래스 이외에도 사용자가 직접 예외 클래스를 정의하여 사용할 수도 있습니다.

사용자 정의 예외클래스 생성방법

1. 예외클래스를 사용자가 직접 정의합니다.
이 때도 일반예외(Exception) 또는 실행예외(RuntimeException) 둘중 하나를 상속받아 사용자 정의 예외클래스를 정의할 수 있습니다.
사용자 예외클래스를 만들 때는 기본생성자와 문자열을 매개변수로 에러메시지를 받는 생성자를 만들어 활용할 수 있습니다.

// 일반예외(Exception)
class MyException extends Exception {
	MyException() {}   //기본 생성자
    MyException(String e) {
    	super(e);      //부모생성자(Exception 클래스) 호출하여 예외메시지 던지기
    }
}
// 실행예외(RuntimeException)
class MyRTException extends RuntimeException {
	MyRTException() {}   //기본 생성자
    MyRTException(String e) {
    	super(e);      //부모생성자(RuntimeException 클래스) 호출하여 예외메시지 던지기
    }
}

2. 작성한 예외클래스를 이용하여 객체를 생성합니다.
정의한 사용자 클래스로 객체를 생성합니다.

MyException myE1 = new MyException();
MyException myE2 = new MyException("에러메세지");
MYRTException myRTE1 = new MyRTException();
MYRTException myRTE2 = new MyRTException("에러메세지");

3. 예외상황에서 예외 객체를 throw합니다.
예외상황일 경우 예외객체를 throw 키워드를 통해 예외객체를 전달할 수 있습니다.
해당 객체를 미리 생성해놓고 객체의 참조변수를 통해 throw로 예외객체를 던질 수도 있고 throw문 내에서 객체를 바로 생성해서 예외객체를 던질 수도 있습니다.

// try-catch 문일 경우
void abc(int age) {
	try {
    	if (age >0) {System.out.println("정상");}
        else {
        	// throw 문 안에서 새로 객체 생성해서 예외객체 전달
        	throw new MyException("에러메세지");
            // 미리 객체 생성 후 객체의 참조변수를 통해서 예외객체 전달
            MYRTException myRTE1 = new MyRTException();
            throw myRTE1;
        }
    } catch(MyRTException) {
    	...
    } catch(MyException) {
    	...
    }
}

// throws 예외전가문일 경우
void abc(int age) throws MyException{
	if (age >0) {System.out.println("정상");}
    else {
    	// throw 문 안에서 새로 객체 생성해서 예외객체 전달
        throw new MyException("에러메세지");
        // 미리 객체 생성 후 객체의 참조변수를 통해서 예외객체 전달
        MyRTException myRTE1 = new MyRTException();
        throw myRTE1;
    }
}

💡 주로 사용하는 예외클래스 내장 메서드
사용자 정의 예외클래스를 정의하게 되면 Exception클래스와 RuntimeException클래스의 부모 클래스의 메서드도 상속받게 됩니다.
이 때 주로 사용할 수 있는 Exception클래스와 RuntimeException클래스 주요 메서드를 알아봅시다!

  • getMessage()
    예외가 발생할 경우 생성자("메시지")로 전달받은 메시지를 문자열로 리턴해주는 메서드 입니다.
    try {
    	throw new MyException();  // 기본 생성자 사용
        throw new MyRTException("메시지 전달"); //생성자("메시지") 사용
    } catch (MyRTException e) {
    	System.out.println(e.getMessage); //생성자("메시지")를 통해 받은 메시지 출력
    } catch (MyException e) {
    	System.out.println(e.getMessage); //기본생성자여서 받은 메시지가 없음 null출력
    }
    위 처럼 사용자 정의 클래스에서 객체 생성 시 작성한 메시지를 받아 이용할 수도 있고, Java 기본 예외클래스에서 정의한 예외메시지를 받아 출력해 예외내용을 확인할 수도 있습니다.
  • printStackTrace()
    예외 발생이 전달되는 경로를 예외가 전가된 경로를 출력해주는 메서드 입니다.
    try {
    	예외 처리가 필요한 코드;
    } catch(Exception e) {
    	e.printStackTrace();  
      // 해당 메서드 자체가 출력기능까지 있는 메서드라 별도의 출력 메서드는 사용하지 않습니다.
    }

참고

Do it! 진짜 개발자가 되는 Java 프로그램 입분서 자바 완전 정복 - 김동형
위 책을 공부하며 작성하고 있습니다!

profile
개발자를 꿈꾸는 병아리 (블로그 이전 준비중 입니다.)

2개의 댓글

comment-user-thumbnail
2024년 2월 9일

안녕하세요! 얼마전 우연찮게 발견했는데 매일매일 꾸준히 포스팅 하셔서 종종 보고갑니다! 오늘은 예외 처리에 관해 작성하셨는데 자세히 공부하신게 느껴지네요. 글 잘 보고갑니다 😁

1개의 답글