[Java] Java의 정석 정리 (예외처리)

송병훈·2022년 10월 6일
0

자바의 정석

목록 보기
15/15
post-thumbnail

학교에서 중간고사를 치루고 채점 중이에요.
시험이 끝나고 채점 중이에요.
1~5번까지는 다 맞았어요. 왠지 느낌이 좋아요.
아! 6번에서 틀렸네요. 너무 슬퍼요.
하지만 채점은 계속 해야겠죠?

예외처리도 마찬가지입니다.
프로그램 실행 중 에러가 발생했다고 해서 종료시키지 않을 거예요.
에러가 발생했어도, 프로그램은 계속 실행시키는 것.
이것이 예외처리의 목적입니다.

멈추지 말고 킵 고잉

그럼 java에서 예외처리는 어떻게 하는 걸까요?


예외처리 (exception handling)

1. 에러의 종류와 예외처리

컴파일 에러:

이클립스나 인텔리제이에서 코드를 작성하다 보면 빨간 밑줄이 생길 때가 있어요.
그 빨간 밑줄을 컴파일 에러라고 불러요.
컴파일 에러가 발생하면 컴파일에 문제가 있는 것이고, 프로그램 실행이 안돼요.
코드의 수정으로 컴파일 에러를 해결할 수 있죠.

런타임 에러:

컴파일 에러가 없는 상황에서 프로그램을 실행시켰어요.
근데 코드의 몇 번째 줄에서 에러가 발생하여 프로그램이 멈췄어요.
이렇게 프로그램 실행 중 어떤 이유로 멈추는 것을 런타임 에러라고 해요.

런타임 에러에서 우리가 수습할 수 있는 미약한 에러를 예외(exception) 라고 해요.
이 예외를 수습하는 것을 예외처리(exception handling) 라고 불러요.
try-catch 문을 사용하면 돼요.

논리적 에러:

프로그램 실행도 문제없이 잘 됐어요.
하지만 개발자가 원하는대로 실행이 안 되었어요.
A를 원했는데 C가 나와버렸어요.

컴파일 에러나 런타임 에러는 없었지만,
코드 자체를 잘못 설계한 거죠. 이것이 논리적 에러 입니다.
코드의 논리흐름을 파악하여 수정해야 돼요.


2. 예외 클래스

java는 객체지향언어죠. 예외도 클래스로 관리해요.

'예외가 발생했어! 어서 해결해줘!'
이렇게 단순한 게 아니라,

'예외가 발생했어. 객체부터 만들자.
이제 이 객체를 통해 예외를 수습해볼까?'

이런 느낌인 거죠.

위 그림은 예외 클래스의 상속구조예요.
최고 조상인 Object 클래스,
그 자손 중 Throwable 클래스는 Exception , Error 클래스로 나눠지죠.
여기서 우리가 수습할 수 있는 클래스는 Exception 클래스 뿐이에요. 예외 클래스죠.

Exception 클래스는
RuntimeException 클래스와
RuntimeException 이 아닌 클래스들 로 나눠져요.

RuntimeException 클래스는
프로그래머의 실수로 발생하고, 예외처리를 꼭 해주지 않아도 돼요.

하지만 RuntimeException 이 아닌 클래스는
외적인 요인에 의해 발생하고, 예외처리를 반드시 해야 합니다.

+++
예외 클래스의 조상인 Throwable 클래스 안에는
예외관련 정보를 다루는 메소드들이 있어요.

  • getMessage() - 발생한 예외객체에 저장된 메세지를 반환
  • getCause() - 예외의 원인을 반환하거나, 원인을 알 수 없는 경우 null을 반환함.
  • initCause() - 예외의 원인을 지정된 값으로 초기화.
  • toString() - 예외에 대한 간단한 설명을 반환.
  • printStackTrace() - 예외발생 당시 '호출스택'에 있던 메소드의 정보와 예외 메세지를 화면에 출력.

그럼 이제 예외처리를 어떻게 하는지
제대로 알아보죠.


3. try-catch문

try-catch?

첫번째로는 try-catch 문으로 예외처리를 합니다.
예외가 발생할 가능성이 있는 코드를 try문에 넣고,
예외 발생 시 어떻게 처리할 것인지 catch문에 넣죠.
catch문의 매개변수로 발생할 예외 클래스를 적어요.

왜 예외 클래스를 catch문의 매개변수로 적을까요?

그 이유는 try문에서 예외가 발생한다는 것
예외의 이유가 되는 클래스의
예외 객체가 만들어지는 것이기 때문입니다.

이 객체가 catch문의 매개변수로 있는
예외 클래스 참조변수로 들어가면서 처리되는 겁니다.
이 객체가 처리되지 않으면 프로그램이 멈추면서 예외 메세지가 빨간글씨로 나오죠.

try문에서 예외가 발생하면,
try문의 이후 코드는 실행하지 않고
바로 catch문을 순서대로 탐색하며
해당하는 catch문으로 이동하여 예외가 처리됩니다.

코드를 보죠

class ExceptionEx5 {
	public static void main(String args[]) {
			System.out.println(1);			
			System.out.println(2);
			try {
				System.out.println(3);
				System.out.println(0/0);	// ArithmeticException 예외객체 생성 (0으로 못 나눔)
				System.out.println(4);		// 실행되지 않는다.
			} catch (ArithmeticException ae)	{	// 예외객체가 들어감
				System.out.println(5);
			}	// try-catch의 끝
			System.out.println(6);
	}	// main메서드의 끝
}

/* 출력
 * 1
 * 2
 * 3
 * 5
 * 6
 * */

catch문의 수

예외가 한 가지만 발생하는 건 아니예요.
여러 예외가 발생할 수 있죠.
그 수만큼 catch문의 개수를 늘릴 수도 있어요.

그리고 모든 예외 클래스의 조상인
Exception 클래스를 마지막 catch문의 매개변수로 둬요.

마지막 catch문에 두는 이유는
Exception 클래스는 모든 예외를 처리하므로
Exception 클래스 뒤에 오는 catch문은
없는 것이나 마찬가지이기 때문이에요.

Exception 클래스를 마지막에 넣지 않았고,
catch문에 명시해놓지 않은 예외가 발생한다면
그 예외는 처리되지 못하고 프로그램이 멈추겠죠.

만일 예외가 발생하지 않으면
catch문은 실행되지 않고 이후 코드를 실행합니다.

class ExceptionEx7 {
	public static void main(String args[]) {
		System.out.println(1);			
		System.out.println(2);
		try {
			System.out.println(3);
			System.out.println(0/0);	// 0으로 나누어서 예외객체 생성됨
			System.out.println(4); 		// 실행되지 않는다.
		} catch (ArithmeticException ae)	{	// 예외객체 들어감
			if (ae instanceof ArithmeticException) 
				System.out.println("true");	
			System.out.println("ArithmeticException");
		} catch (Exception e)	{	// Exception은 마지막 catch문에 둔다.
			System.out.println("Exception");
		}	// try-catch의 끝
		System.out.println(6);
	}	// main메서드의 끝
}

/* 출력
 * 1
 * 2
 * 3
 * true
 * ArithmeticException
 * 6
 * */

finally 블럭

예외의 발생여부와 상관없이 실행되어야 하는 코드를 finally에 넣습니다.
예외가 발생하면 try - catch - finally 순으로,
예외가 발생하지 않으면 try - finally 순으로 실행됩니다.

try 혹은 catch에서 return문을 만나도 finally 블럭은 수행됩니다.

class FinallyTest3 {
	public static void main(String args[]) {
		// method1()은 static메서드이므로 인스턴스 생성없이 직접 호출이 가능하다.
		FinallyTest3.method1();		
        System.out.println("method1()의 수행을 마치고 main메서드로 돌아왔습니다.");
	}	// main메서드의 끝

	static void method1() {
		try {
			System.out.println("method1()이 호출되었습니다.");
			return;		// 현재 실행 중인 메서드를 종료한다.
		}	catch (Exception e)	{
			e.printStackTrace();
		} finally {
			System.out.println("method1()의 finally블럭이 실행되었습니다.");
		}
	}	// method1메서드의 끝
}

/* 출력
 * method1()이 호출되었습니다.
 * method1()의 finally블럭이 실행되었습니다.
 * method1()의 수행을 마치고 main메서드로 돌아왔습니다.
 * */

4. throw, throws

예외를 발생시키자 throw

예외 클래스가 있으니 인스턴스를 생성할 수도 있겠죠?

예외 클래스의 인스턴스를 생성하고,
throw 키워드를 앞에 붙이면
개발자가 의도적으로 예외를 발생시킬 수 있어요.

이렇게요.

class ExceptionEx10 {
	public static void main(String[] args) {
		throw new Exception();		// Exception을 고의로 발생시킨다.
	}
}

class ExceptionEx11 {
	public static void main(String[] args) {
		throw new RuntimeException();	// RuntimeException을 고의로 발생시킨다.
	}
}

예외처리를 넘겨버리자 throws

1. 예외처리를 바로 하는 경우

기본적인 경우죠.

class ExceptionEx13 {
	public static void main(String[] args) {
		method1(); 	  // 같은 클래스내의 static멤버이므로 객체생성없이 직접 호출가능.
  	}	// main메서드의 끝

	static void method1() {
		try {
		     throw new Exception();		// 예외객체 생성
		} catch (Exception e) {			// 예외처리
			System.out.println("method1메서드에서 예외가 처리되었습니다.");
			e.printStackTrace();
		}
	}	// method1의 끝
}
  1. main메소드에서 method1() 메소드 호출
  2. method1() 메소드에서 예외발생 및 예외처리

2. 예외처리를 넘겨주는 경우

메소드를 호출한 곳에서 예외처리를 해야 할 때 사용해요.

class ExceptionEx14 {
	public static void main(String[] args) {
		try  {
				method1();		
		} catch (Exception e)	{
				System.out.println("main메서드에서 예외가 처리되었습니다.");
				e.printStackTrace();
		}
	}	// main메서드의 끝

	static void method1() throws Exception {
		throw new Exception();
	}	// method1()의 끝
} // class의 끝
  1. main메소드에서 method1() 메소드 호출
  2. method1() 메소드에서 예외발생
  3. throws 키워드와 예외 클래스명을 method1() 메소드 옆에 붙여서
    메소드를 호출한 main메소드로 해당 예외클래스의 예외처리를 넘김
  4. method1() 메소드 호출하며 발생된 예외가 main메소드로 넘어옴
  5. main메소드의 catch문에서 예외처리

3. 예외처리 후, 다시 생성해서 넘겨주는 경우

메소드를 호출한 곳, 호출된 메소드 양쪽에서 예외를 처리해야 하는 경우에 사용해요.

class ExceptionEx17 {
	public static void main(String[] args) {
		try  {
			method1();		
		} catch (Exception e)	{
			System.out.println("main메서드에서 예외가 처리되었습니다.");
		}
	}	// main메서드의 끝

	static void method1() throws Exception {
		try {
			throw new Exception();
		} catch (Exception e) {
			System.out.println("method1메서드에서 예외가 처리되었습니다.");
			throw e;			// 다시 예외를 발생시킨다.
		}
	}	// method1메서드의 끝
}
  1. main메소드에서 method1() 메소드 호출
  2. method1() 메소드에서 예외발생, 예외처리, 예외 재발생
  3. throws 키워드와 예외 클래스명을 method1() 메소드 옆에 붙여서
    메소드를 호출한 main메소드로 해당 예외클래스의 예외처리를 넘김
  4. method1() 메소드 호출하며 재발생된 예외가 main메소드로 넘어옴
  5. main메소드의 catch문에서 다시 예외처리

5. 사용자 정의 예외

간단하게 설명하고 넘어갈게요.
우리가 클래스를 만들 수 있는 것과 마찬가지로
예외 클래스도 만들 수 있어요.

기존의 예외 클래스인 Exception , RuntimeException 등의
예외 클래스를 상속받은 새로운 클래스를 정의하면 돼요.

class SpaceException extends Exception {
	SpaceException(String msg) {
	   super(msg);	
   }
} 

예외 클래스의 정보를 더해주기 위해
에러코드를 저장하는 상수나,
해당 에러코드를 반환하는 메소드를 만들 수도 있어요.

class MyException extends Exception {
	private final int ERR_CODE;		// 에러코드를 저장하기 위한 멤버
	
    MyException() {} // 기본 생성자
    
    MyException(String msg, int errCode) { // 생성자
    	super(msg);
        ERR_CODE = errCode;
    }
	
    MyException(String msg) {	// 생성자
    	this(msg,100);			// ERR_CODE를 100으로 초기화한다.
    }
    
    public int getErrCode() {
    	return ERR_CODE;
	}
}

'연결된 예외' 라는 개념도 있는데, 양이 너무 많아져서
예외처리는 여기까지만 하고 넘어가도록 하겠습니다.
감사합니다!!

profile
성실하고 꼼꼼하게

0개의 댓글