-
[Java] 자바의 예외Programming/Java 2021. 4. 6. 15:53
자바의 예외와 그 처리방법을 알아보자
1. 프로그램 오류와 예외 클래스 계층 구조
1-1) 프로그램 오류의 종류
프로그램의 오류는 크게 3가지가 있다.
- 컴파일 에러 : 컴파일할 때 발생하는 에러를 일컫는다.
- 런타임 에러 : 실행 중에 발생하는 에러를 일컫는다.
- 논리적 에러 : 의도와 다르게 동작하는 것을 일컫는다.
Java에서 정의한 런타임 에러(실행 중 발생하는 에러)
에러(error)
: 프로그램 코드에 의해 수습될 수 없는 심각한 오류- OutOfMemoryException...
예외(exception)
: 프로그램 코드에 의해서 수습될 수 있는 다소 미약한 오류
1-2) 예외처리(exception handling)의 정의와 목적
- 정의 : 프로그램 실행 시 발생할 수 있는 예외의 발생에 대비한 코드를 작성하는 것
- 목적 : 프로그램의 비정상 종료를 막고, 정상적인 실행상태를 유지하는 것
1-3) 예외 클래스의 계층 구조
Object
: 최고조상Throwable
: 모든 오류의 조상Error
: 시스템 구조상의 문제로 발생하는 심각한 오류Exception
: 프로그램의 알고리즘이나 실행 절차상의 문제로 발생하는 경미한 오류
1-4) Exception과 RuntimeException
Exception 클래스는 다시
Exception의 자손
과RuntimeException의 자손
으로 구분지을 수 있다. (RuntimeException또한 Exception의 자손이다)Exception 클래스와 그 자손
: 사용자의 실수와 같은 외적인 요인에 의해 발생하는 예외- I/OException(입출력예외), ClassNotFoundException(사용하려는 클래스가 존재하지 않음)...
RuntimeException 클래스와 그 자손
: 프로그래머의 실수로 발생하는 예외- ArithmeticException(잘못된 계산), ClassCastException(잘못된 형변환), NullPointerException(객체가 null), IndexOutOfBoundaryException(배열 범위 벗어남)...
2. 예외처리하기, try-catch문의 흐름
2-1) 예외처리(exception handling)의 정의와 목적
- 정의 : 프로그램 실행 시 발생할 수 있는 예외의 발생에 대비한 코드를 작성하는 것
- 목적 : 프로그램의 비정상 종료를 막고, 정상적인 실행상태를 유지하는 것
예외가 발생할 가능성이 있는 문장을
try
블럭에 넣고, 예외가 발생했을 때 처리할 문장을catch
문장에 넣는다. 괄호를 생략할 수 없음에 주의하자!try{ // 예외가 발생할 가능성 있는 문장.. } catch (Exception1 e1){ // Exception1이 발생할 경우, 이를 처리하기 위한 문장 } catch (Exception2 e2){ // Exception2이 발생할 경우, 이를 처리하기 위한 문장 } catch (Exception3 e3){ // Exception3이 발생할 경우, 이를 처리하기 위한 문장 }
2-2) try-catch 문에서의 흐름
1. try블럭에서 예외가 발생한 경우
- 발생한 예외와 일치하는 catch 블럭이 있는지 확인
- 일치하는 catch 블럭을 찾게 되면, 그 catch 블럭 내의 문장들을 수행하고 전체 try-catch문을 빠져나가서 그 다음 문장을 계속 수행한다. 만일 일치하는 catch블럭을 찾지 못하면, 예외는 처리되지 못한다.
- try 블럭 내에서 예외가 발생하면, 예외발생문장 뒤의 문장들을 수행되지 않는다.
2. try 블럭 내에서 예외가 발생하지 않은 경우
- catch 블럭을 거치지 않고 전체 try-catch문을 빠져나가서 수행을 계속한다.
예제 : ArithmeticException(0으로 나누는 예외) 발생시키고 처리하기
class ExceptionTest { public static void main(String[] args) { System.out.println(1); try { System.out.println(1 / 0); // 0으로 나누기 오류 System.out.println(2); // 예외발생한 다음문장은 실행되지 않는다. } catch (ArithmeticException ae) { System.out.println(3); // 예외처리 문장 } catch (Exception e) { // 모든 예외의 최고 조상 System.out.println("Exception"); } System.out.println(4); } }
1 3 4
3. printStackTrace()와 멀티 catch 블럭
3-1) printStackTrace()와 getMessage()
printStackTrace()
: 예외발생 당시의 호출스택(Call Stack)에 있던 메서드의 정보와 예외 메세지를 화면에 출력한다.getMessage()
: 발생한 예외클래스의 인스턴스에 저장된 메세지를 얻는다.
예제 : ArithmeticException 처리
public class ExceptionTest { public static void main(String[] args) { try { System.out.println(1 / 0); // 0으로 나누기 오류 } catch (ArithmeticException ae) { ae.printStackTrace(); System.out.println(ae.getMessage()); } } }
java.lang.ArithmeticException: / by zero at javabasic.week9.ExceptionTest.main(ExceptionTest.java:6) / by zero
코드 실행 순서
1. 0으로 나누는 예외 발생
2. ArithmeticException 타입의 (예외)객체 생성- 객체에는 발생한 예외에 대한 정보가 들어있으며, printStackTrace(), getMessage() 등 정보를 가져올 수 있는 메서드를 가지고 있다.
3. 예외를 처리할 수 있는 catch 블럭이 있는지 확인한다.
- 발생한 예외객체 타입과 catch블럭의 참조변수 타입이 일치한다(ArithmeticException).
4. 발생한 객체의 주소가 catch블럭의 참조변수
ae
에 들어간다.- 즉 참조변수(ae)가 예외객체를 가리키게 된다.
- 이 때 참조변수(ae)를 사용할 수 있는 유효범위(scope)는 해당 catch블럭이 끝날 때까지이다.
5. 참조변수를 통해 객체에 담긴 예외정보를 가져온다.
3-2) 멀티 catch 블럭
멀티 catch 블럭이란, 코드 중복을 제거하기 위해서 내용이 같은 catch 블럭을 하나로 합친 것을 말한다. (JDK 1.7부터)
멀티 catch 사용 전의 try-catch 문
예외 처리 문장이 같더라도 catch블록을 따로 작성해야 한다.
try { ... } catch(ExceptionA e){ ... } catch(ExceptionB e2){ ... }
멀티 catch 사용 후의 try-catch 문
예외 처리 문장이 같다면 catch블록을 묶어서 사용한다.
try{ ... } catch (ExceptionA | ExceptionB e){ ... }
멀티 catch문을 사용할 때 주의할 점
1. 부모-자식관계인 예외클래스를 사용
부모타입의 참조변수가 선언된 catch블록을 선언하면, 해당 부모를 상속받은 자손은 모두 해당 catch 블럭에서 예외처리 된다. 따라서 굳이 따로 자식타입의 catch 블록을 선언할 필요없다.
오류 : 부모-자식 예외클래스를 함께 선언한 catch블록 사용 (에러 발생)
try{ ... } catch (ParentException | ChildException e){ // 에러 발생 }
올바른 방법
try{ ... } catch (ParentException e){ ... }
2. 예외클래스 A와 B의 공통멤버만 사용가능
두 개의 클래스에 모두 존재하는 멤버만 사용할 수 있다.
try{ ... } catch (ExceptionA | ExceptionB e){ // e.methodA(); // error! : ExceptionA만 선언된 메서드 호출불가 if (e instanceof ExceptionA){ ExceptionA e1 = (ExceptionA) e; e1.methodA(); // 형변환 후에 사용 가능 } }
4. 예외 발생시키기
4-1) 예외 발생시키기
1. 연산자 new를 이용해서 발생시키려는 예외 클래스의 객체를 만든 다음
Exception e = new Exception("고의로 발생시킨 예외");
2. 키워드 throw를 이용해서 예외를 발생시킨다.
예제 : 예외 고의로 생성하고 처리하기
public class ExceptionTest { public static void main(String[] args) { try { Exception e = new Exception("고의로 발생시킨 예외"); throw e; // throw new Exception("고의로 발생시킨 예외"); } catch (Exception e) { System.out.println("에러 메세지:" + e.getMessage()); e.printStackTrace(); } System.out.println("프로그램 정상 종료"); } }
에러 메세지:고의로 발생시킨 예외 프로그램 정상 종료 java.lang.Exception: 고의로 발생시킨 예외 at javabasic.week9.ExceptionTest.main(ExceptionTest.java:8)
4-2) checked 예외, unchecked 예외
checked 예외
: 컴파일러가 예외처리여부를 체크(예외 처리 필수)- Exception과 그 자손
unchecked 예외
: 컴파일러가 예외처리여부를 체크 안함(예외 처리 선택)- RuntimeException과 그 자손
예제 : checked 예외 발생시키기
예외처리 안하면 컴파일 에러를 발생시킨다.
public class ExceptionTest { public static void main(String[] args) { throw new RuntimeException("unchecked 예외"); } }
java: unreported exception java.lang.Exception; must be caught or declared to be thrown
예제 : unchecked 예외 발생시키기
예외처리 안하면 런타임 에러를 발생시켜, 프로그램이 비정상적으로 종료된다.
public class ExceptionTest { public static void main(String[] args) { throw new RuntimeException("unchecked 예외"); } }
Exception in thread "main" java.lang.RuntimeException: unchecked 예외 at javabasic.week9.ExceptionTest.main(ExceptionTest.java:7)
4. 메서드에 예외 선언하기, finally 블럭
4-1) 메서드에 예외 선언하기
- 예외 처리 방법 : try-catch문(직접처리), 예외선언하기(떠넘기기)
- 메서드가 호출되면 발생가능한 예외를 호출하는 쪽에 알리는 것
- (cf) 예외를 발생시키는 키워드 throw와 예외를 메서드에 선언할 때 쓰이는 throws 잘 구별하기
메서드의 예외 선언 방법
메서드를 호출해서 사용하는 이에게, 이러한 예외가 발생할 수 있음을 알려준다.
- 여러 개의 예외를 선언할 수 있다.
- 메서드를 호출한 쪽에서는 해당 예외들에 대한 try-catch블럭을 사용하거나 또 다시 메서드에 예외를 선언하여 예외를 처리할 수 있다.
- (주의:exclamation:) unchecked/checked 예외 모두 선언가능하지만, 정석적으로는 checked예외(필수처리예외)만 선언한다.
// 메서드에 예외 선언 void method() throws Exception1, Exception2, Exception3 ....{ // 메서드 내용 }
:point_right: Exception 예외 선언
- Exception은 모든 예외의 조상이다.
- 따라서 해당 클래스를 메서드에 선언하면 모든 종류의 예외가 발생가능하다.
// 모든 종류의 예외 발생 가능 // method()에서 Exception과 그 자손 예외 발생 가능 void method() throws Exception{ // 메서드 내용 }
예제 : 프로그램 설치 시 예외처리
- 설치를 진행하는 도중, 공간이 부족해서 또는 메모리가 부족해서 정상작업이 불가능할 수 있다.
- 이 때
SpaceException
,MemoryException
을 발생시켜 자신을 호출한 쪽에 문제가 발생했음을 알린다.
static void startInstall() throws SpaceException, MemoryException{ // 충분한 설치 공간이 없을 경우 if (!enoughSpace()) throw new SpaceException("설치할 공간이 부족합니다."); // 충분한 메모리가 없을 경우 if (!enoughMemory()) throw new MemoryException("메모리가 부족합니다"); }
예제2 : 예외처리의 흐름
public static void main(String[] args) throws Exception{ method1(); } static void method1() throws Exception{ method2(); } static void method2() throws Exception{ throw new Exception(); }
예외처리순서
- main()이 method1()을 호출
- method1()이 method2()를 호출
- method2()에서 예외 발생
- method2()는 예외 처리하지 않고 죽음
- method2()를 호출한 method1()이 method2()의 예외를 받음
- method1()은 예외 처리하지 않고 죽음
- method1()을 호출한 main()이 method1()으로부터 예외를 받음
- main()이 예외 처리하지 않고 죽음
- main 메서드가 죽으면서 비정상 종료가 되고, 이 예외는 JVM에게 넘겨진다.
- JVM이 받아서 마지막으로 예외를 처리함
콘솔 (JVM의 기본예외처리기가 printStackTrace()로 출력한 내용)
Exception in thread "main" java.lang.Exception at javabasic.week9.ExceptionTest.method2(ExceptionTest.java:14) at javabasic.week9.ExceptionTest.method1(ExceptionTest.java:10) at javabasic.week9.ExceptionTest.main(ExceptionTest.java:6)
예제3 : 파일입출력
- 파일이름이 null값이면 Exception을 main으로 던져서 다시 입력받게끔
class ExceptionTest { public static void main(String[] args) { try { File f = createFile("exception_test.txt"); System.out.println(f.getName()+"파일이 성공적으로 생성되었습니다."); } catch(Exception e){ System.out.println(e.getMessage()+" 다시 입력해주시기 바랍니다."); } } static File createFile(String fileName) throws Exception{ if (fileName == null || fileName.equals("")) throw new Exception("파일 이름이 유효하지 않습니다."); File f = new File(fileName); // file 클래스 객체 생성 f.createNewFile(); // file 객체의 createNewFile 메서드로 파일 생성 return f; // 생성된 객체 참조 반환 } }
exception_test.txt파일이 성공적으로 생성되었습니다.
4-3) finally 블럭
- 예외 발생 여부와 관계없이 수행되어야 하는 코드를 넣는다.
- (cf) try 블럭 안에 return 문이 있어서 try 블럭을 벗어나는 경우에도, finally 블럭을 실행한 뒤에 return된다.
finally 블럭 사용 방법
try{ // 예외가 발생할 가능성이 있는 문장들을 넣는다. } catch (Exception1 e1){ // 예외처리를 위한 문장을 적는다. } finally{ // 예외의 발생여부와 관계없이 항상 수행되어야 하는 문장들을 넣는다. // finally 블럭은 try-catch문의 맨 마지막에 위치해야 한다. }
예시 : 설치 임시파일 삭제
finally 블럭 사용 전
try { startInstall(); copyFiles(); deleteTempFiles(); } catch(Exception e){ e.printStackTrace(); deleteTempFiles(); }
finally 블럭 사용 후
- try블럭과 catch 블럭에서 모두 사용되던 코드중복을 제거한다.
try { startInstall(); copyFiles(); } catch(Exception e){ e.printStackTrace(); } finally { deleteTempFiles(); }
5. 사용자 정의 예외 만들기, 예외 되던지기
5-1) 사용자 정의 예외 만들기
우리가 직접 예외 클래스를 만들어서 사용할 수 있다. Exception(실제 사용자가 발생시키는 예외) 클래스 혹은 RuntimeException(프로그래머의 실수로 발생시키는 예외) 클래스를 상속받아서 만들 수 있다.
- Exception(필수처리), RuntimeException(선택처리) 이므로 가능한한 RuntimeException을 상속받아서 사용하는 것이 사용에 자유롭다.
- 예외 메세지를 받는 생성자를 만드는 것이 좋다. 블럭 내에서 조상생성자를 호출하도록 해야한다.
MyException(String msg)
{ super(msg);} // 조상클래스의 생성자 Exception(String msg)호출
예제 : 사용자 예외 MyException 만들기
// Exception(필수처리 예외)를 상속받은 클래스 // try-catch 필요하다. class MyException extends Exception{ // 에러 코드값을 저장하기 위한 필드(필수는 아니지만 에러 코드 사용해서 쓸 수도 있다.) private final int ERR_CODE; // String 매개변수를 가진 생성자로 에러 메세지 세팅 MyException(String msg){ this(msg,100);// ERR_CODE를 100(기본값)으로 초기화 } MyException(String msg, int errCode){ super(msg); ERR_CODE = errCode; } // 에러 코드를 얻을 수 있는 메서드 // 주로 getMessage()와 함께 사용될 것임 public int getErrCode() { return ERR_CODE; } }
5-2) 예외 되던지기 (exception re-throwing)
- 예외를 처리한 후에 다시 예외 발생시킴
- 호출한 메서드와 호출된 메서드 양쪽에서 모두 예외처리
예제 : 예외 되던지기
class ExceptionTest { public static void main(String[] args) { try { method1(); // 5) 예외 받음 } catch (Exception e) { // 6) 받은 예외 처리 System.out.println("main 메서드에서 예외가 처리되었습니다."); } } static void method1() throws Exception { // 4) 호출된 쪽으로 예외 던지기 try { throw new Exception(); // 1)예외발생 } catch (Exception e) { // 2) 예외 처리 System.out.println("method1 메서드에서 예외가 처리되었습니다."); throw e; // 3) 다시 예외를 발생시킴 } } }
method1 메서드에서 예외가 처리되었습니다. main 메서드에서 예외가 처리되었습니다.
6. 연결된 예외
한 예외가 다른 예외를 발생시킬 수 있다. 예외 A가 예외 B를 발생시키면, A는 B의 원인 예외(cause Exception)이다. 이렇게 두 가지 예외를 연결하는 것을
연결된 예외
라 부른다.예외 연결을 위한 메서드
Throwable initCause(Throwable cause)
: 지정한 예외(cause
)를 원인 예외로 등록Throwable getCause()
: 원인 예외를 반환
예제 : 하나의 예외 안에 또다른 예외를 포함시키기
Throwable
:Exception
과Error
의 조상- 여기서는 Exception으로 이해하면 된다.
public class Throwable implements Serializeable{ private Throwable cause = this; // 원인 예외 저장을 위한 변수 ... // 원인 예외 초기화 메서드 public synchronized Throwable initCause(Throwable cause){ ... this.cause = cause; // cause를 원인 예외로 등록 return this; } }
예제2 : SpaceException을 InstallException과 연결해서 사용
- 실제 발생한 예외(SpaceException)를 새로운 예외(InstallException)에 포함시키고
- 새로운 예외(InstallException)를 호출한 곳으로 던진다.
void install() throws InstallException { try { startInstall(); // spaceException(저장공간부족) 발생 copyFiles(); } catch (SpaceException e){ InstallException ie = new InstallException("설치중 예외 발생"); // 새로운 예외 InstallException 생성 ie.initCause(e); // InstallException의 원인 예외로 SpaceException을 지정 throw ie; // InstallException을 발생시킴. } }
연결된 예외 사용이유1 : 여러 예외를 하나로 묶어서 다루기 위함
1. 예외 연결 전
- 발생가능한 예외가 많다면 catch블럭 수가 무한정 늘어나야 함.
try{ install(); } catch(SpaceException e){ e.printStackTrace(); } catch(MemoryException e){ e.printStackTrace(); } catch(Exception e){ e.printStackTrace(); }
결과
SpaceException: 설치할 공간이 부족합니다. at ExceptionTest.install(ExceptionTest.java:22) at ExceptionTest.main(ExceptionTest.java:4)
2. 예외 연결 후
- 발생가능한 예외를 하나의 예외에 포함시켜서 하나의 catch블럭으로 예외를 처리할 수 있다.
try{ install(); } catch(InstallException e){ e.printStackTrace(); } catch(Exception e){ e.printStackTrace(); }
결과
- 대략적인 정보와 세부정보를 나눠서 볼 수 있음
InstallException: 설치중 예외발생 at ExceptionTest.install(ExceptionTest.java:17) at ExceptionTest.main(ExceptionTest.java:4) Caused by: SpaceException: 설치할 공간이 부족합니다. at ExceptionTest.startInstall(ExceptionTest.java:31) at ExceptionTest.install(ExceptionTest.java:14) ... 1 more
연결된 예외 사용이유2 : checked 예외를 unchecked 예외로 변경하기 위함 (필수처리예외를 선택처리예외로 변경)
예제 : 선택처리예외로 변경하기
변경 전
- SpaceException과 MemoryException은 Exception 클래스의 자손으로 checked(예외 필수처리) 예외이다.
static void startInstall() throws SpaceException, MemoryException{// Exception의 자손만 선언 if (!enoughSpace()) throw new SpaceException("설치할 공간이 부족합니다."); // Checked 예외 if (!enoughMemory()) throw new MemoryException("메모리가 부족합니다."); // Checked 예외 }
변경 후
- RuntimeException을 생성해서 해당 예외 안에 포함시킨다.
- 1번째 경우처럼 initCause를 사용하지 않고, RuntimeException의 생성자에 원인 예외를 넣어준다.
- MemoryException은 선택처리 예외가 되었으므로, 메서드에 선언하지 않는다.
static void startInstall() throws SpaceException{ // MemoryException은 선택처리예외로 변경 if (!enoughSpace()) throw new SpaceException("설치할 공간이 부족합니다."); if (!enoughMemory()) // 원인 예외로 등록 throw new RuntimeException(MemoryException("메모리가 부족합니다.")); }
References
'Programming > Java' 카테고리의 다른 글
[Java] 자바의 열거형(Enum) (0) 2021.04.06 [Java] 자바의 멀티 스레드 프로그래밍 (0) 2021.04.06 [Java] 자바의 인터페이스 (0) 2021.04.06 [Java] 자바의 패키지와 클래스패스 (0) 2021.04.05 [Java] 자바의 다형성 (0) 2021.04.05 댓글