* 이 글은 제가 공부하기 위해 최대한 사실에 입각해 내용을 적으려고 하지만 일부 내용들이 정확하지 않을 수 있습니다.
혹시나 잘못된 부분이 있으면 너그럽게 이해해주시고 피드백 부탁드려요!
11장 예외처리(에러를 미리 대비하자)
11.1 예외란
프로그램 실행 중에 무엇인가 의해서 오작동을 하거나 비 정상적으로 종료되는 경우가 있다. 우리는 흔히 에러(error)라고 말한다. 보통 에러가 발생하면 프로그램은 에러가 발생한 곳에서 멈추게 된다. 자바 프로그램에서 에러는 JVM에서 실행 중 문제가 생긴 것으로 이런 에러는 개발자가 대처할 수 있는 방법은 없다. 예외란 문법적인 오류가 없어 프로그램이 실행은 되지만 특수한 상황을 만나면 프로그램이 중단되는 현상으로 에러 중에서 대처할 수 있는 에러라고 말할 수 있다. 예외 처리는 예외를 방치하거나 에러로 인한 프로그램이 종료되지 않고 계속 작업을 처리하도록 해주거나 예외 내용을 확인하기 위한 작업니다.
에러 : 프로그램 코드에 의해서 해결할 수 없는 심각한 오류
예외 : 특수한 상황이 발생하면 프로그램이 중단되는 현상으로 프로그램 코드에 의해서 해결 할 수 있는 오류
이 에러를 발생시점에 따란 컴파일 에러(compile error)와 런타임 에러(runtime error)로 나눌 수 있는데, 글자 그대로 컴파일 에러는 컴파일 할 때 발생하는 에러이고 프로그램의 실행 도중에 발생하는 에러를 런타임 에러라고 한다.
자바에서는 실행(runtime) 시 발생할 수 있는 프로그램 오류를 에러(error)와 예외(exception) 두 가지로 구분하고 있는데, 에러는 메모리 부족(OutOfMemoryError)이나 스택오버플로우(StackOverflowError)와 같이 일단 발생하면 복구할 수 없는 심각한 오류이고, 예외는 발생하더라도 처리될 수 있는 비교적 덜 심각한 오류이다. 에러가 발생하면, 프로그램의 비정상적인 종료를 막을 길이 없지만, 예외는 발생하더라도 프로그래머가 이에 대한 적절한 코드를 미리 작성해 놓음으로써 프로그램의 비정상적인 종료를 막을 수 있다.
11.2 예외 클래스
자바는 모든 오류를 클래스로 제공하고 있는데, 모든 비정상적인 동작을 Throwble라는 클래스로 표현하고 다시 Error와 Exception 클래스로 나눈다. Exception 클래스 자손들의 예외가 발생하면 덜 치명적인 오류라고 보고 프로그램을 강제로 종료하는 것보다는 오류 메세지 등을 내보내고 오류 발생 가능성이 있는 부분에 대해서 미리 프로그램으로 처리를 해주는 것이다. 즉 예외처리 대상은 Exception 클래스 및 자손 클래스들이다.
예외 클래스는 java.lang 패키지 내에 속하지만 IOException 클래스 및 그 하위 클래스는 java.io 패키지에 속한다.
Error 클래스의 하위 클래스는 다음과 같다. 그 외에도 무수히 많은 하위 클래스가 있지만 자세한 내용은 API를 참조하기 바란다.
이제 Exception 클래스의 하위 클래스들을 살펴보면 기술한 클래스가 Exception 클래스의 전부는 아니지만, 가장 많이 다루어지는 예외 클래스들만 살펴보도록 하겠다.
예외 클래스 | 예외 발생 원인 |
RuntimeException | 실행 중 예외가 발생 |
CloneNotSupportException | 객체가 복제되지 않은 상태에서 복제 시도 |
InterruptedException | 쓰레드가 중지된 경우 |
NoSuchMethodException | 메서드가 없는 경우 |
ClassNotFoundException | 클래스를 찾지 못하는 경우 |
IOException | 입출력관련 예외가 발생하는 경우 |
예외 클래스 | 예외 발생 원인 |
ArithmeticException | 0으로 나누는 경우 |
NegativeArraySizeException | 배열의 크기가 음수인 경우 |
NullPointerException | null 객체에 접근하는 경우 |
ClassCastException | 객체가 형변환이 잘못된 경우 |
IndexOutOfBoundException | 인덱스의 범위를 벗어나는 경우 |
11.3 예외 처리
try 블록 안에는 예외가 발생할 가능성이 있는 문장 코드를 넣고 예외가 발생하면 catch 블록에서 처리한다. catch 블록은 예외가 발생하지 않으면 실행되지 않는다.
try ~ catch문의 구조를 살펴보자.
try { // 예외가 발생할 가능성이 있는 문장 코드 } catch(Exception1 e1) { // Exception1이 발생할 경우, 실행될 문장 } catch(Exception2 e2) { // Exception2이 발생할 경우, 실행될 문장 ... } catch(ExceptionX eX) { // ExceptionX이 발생할 경우, 실행될 문장 } |
하나의 try 블록 밑에는 여러 종류의 예외를 처리할 수 있도록 여러 개의 catch 블록이 올 수 있는데, 이 중 발생한 예외의 종류와 일치하는 한 개의 catch블록만 수행되고, try~catch문은 종료된다. 발생한 예외의 종류와 일치하는 catch블록이 없으면 예외는 처리되지 않는다. 주의할 점은 try~catch문은 if문이나 for문, while문과 같이 한줄이라고 해서 중괄호를 생략할 수 없다.
try~catch문은 예외가 발생한 경우와 발생하지 않은 경우의 실행문의 실행순서가 달라지는데, 이 두가지의 경우를 다시 정리해보자.
※ try 블록 안에서 예외가 발생한 경우
- 발생한 예외와 일치하는 catch문이 있는지 확인한다.
- 만약 일치하는 catch문이 있다면, 해당 catch문의 블록내의 실행문들을 실행하고, 전체 try-catch 구문이 종료된다.
만약 일치하는 catch문이 없으면 예외 처리를 하지 못한다.
※ try 블록 안에서 예외가 발생하지 않은 경우
- catch 구문을 모두 확인하지 않고, 전체 try-catch 구문이 종료된다.
package chapter11;
public class ExceptionEx0 {
public static void main(String[] args) {
System.out.println(1);
System.out.println(2);
System.out.println(3/0);
System.out.println(4);
System.out.println(5);
System.out.println(6);
}
}
package chapter11;
public class ExceptionEx {
public static void main(String[] args) {
System.out.println(1);
try {
System.out.println(2);
System.out.println(3/0); // 예외 발생, 출력이 안나옴.
System.out.println(4); // 출력이 안나옴.
}
catch (ArithmeticException e) { // 예외 잡음
System.out.println(5);
}
System.out.println(6); // 출력된 이유 정상적으로 종료되었다는 점
}
}
package chapter11;
public class ExceptionEx2 {
public static void main(String[] args) {
System.out.println(1);
try {
System.out.println(2);
System.out.println(3);
System.out.println(4);
}
catch (ArithmeticException e) { // 예외가 없기 때문에 출력 안됨.
System.out.println(5);
}
System.out.println(6);
}
}
package chapter11;
public class ExceptionEx3 {
public static void main(String[] args) {
System.out.println(1);
try {
System.out.println(2);
System.out.println(3/0);
System.out.println(4);
}
catch (Exception e) { // 발생된 예외는 아리스매틱인데 그냥 익셉션으로 바꿔도 실행
System.out.println(5);
}
System.out.println(6);
}
}
다중 catch문
여러 개의 catch문이 존재하는 구문으로 발생된 예외별로 다른 예외처리를 할 수 있다.
※ catch 문의 배치 순서
- 자식 Exception > Exception 순으로 배치
package chapter11;
public class ExceptionEx4 {
public static void main(String[] args) {
System.out.println(1);
try {
System.out.println(2);
System.out.println(3/0);
System.out.println(4);
}
catch (ArithmeticException e) { // e는 블록 내 지역변수, 아무거나 써도 상관없음
System.out.println(4.5);
}
catch (Exception e) { // 5 출력 안됨, catch는 여러개 올수 있고 하나 catch가 되면 남은 catch안함
System.out.println(5);
}
System.out.println(6);
}
}
package chapter11;
public class ExceptionEx5 {
public static void main(String[] args) {
System.out.println(1);
try {
System.out.println(2);
System.out.println(3/0);
System.out.println(4);
}
catch (Exception e) { // 계층관계에서 익셉션 처리 가능
System.out.println(5);
}
catch (ArithmeticException e) { // 조상이 처리하고 자손이 처리못하기 때문에 에러 발생
// 결국 캐치를 쓸 때는 자손먼저 쓰면서 해야함
System.out.println(4.5);
}
System.out.println(6);
}
}
package chapter11;
public class ExceptionEx6 {
public static void main(String[] args) {
System.out.println(1);
try {
System.out.println(2);
System.out.println(args[0]); // 익센셥 예외처리 먼저
System.out.println(3/0);
System.out.println(4);
}
catch (ArithmeticException e) { // 4.5 출력 안됨
System.out.println(4.5);
}
catch (Exception e) { // 5 출력
System.out.println(5);
}
System.out.println(6);
}
}
finally 구문
finally는 마지막에 실행된다는 의미로, 예외없이 정상적으로 실행이 되던, 예외가 발생하던 무조건 실행되는 구문이다.
package chapter11;
public class ExceptionEx1 {
public static void main(String[] args) {
m1();
}
static void m1() {
m2();
}
static void m2() {
System.out.println(1);
System.out.println(2/0);
System.out.println(3);
}
}
package chapter11;
public class ExceptionEx1_1 {
public static void main(String[] args) {
System.out.println("main() start");
m1();
System.out.println("main() end");
}
static void m1() {
System.out.println("m1() start");
m2();
System.out.println("m1() end");
}
static void m2() {
System.out.println("m2() start");
System.out.println(1);
try {
System.out.println(2/0);
}
catch(NullPointerException e) {
System.out.println(2.5);
}
finally {
System.out.println(2.7);
}
System.out.println(3);
System.out.println("m2() end");
}
}
package chapter11;
public class ExceptionEx1_2 {
public static void main(String[] args) {
System.out.println("main() start");
m1();
System.out.println("main() end");
}
static void m1() {
System.out.println("m1() start");
try {
m2();
}
catch (ArithmeticException e) {
System.out.println("m1() 예외처리");
}
System.out.println("m1() end");
}
static void m2() {
System.out.println("m2() start");
System.out.println(1);
try {
System.out.println(2/0);
}
catch(NullPointerException e) {
System.out.println(2.5);
}
finally {
System.out.println(2.7);
}
System.out.println(3);
System.out.println("m2() end");
}
}
package chapter11;
public class ExceptionEx1_3 {
public static void main(String[] args) {
System.out.println("main() start"); // System.err 로 하면 빨강색으로 나옴.
m1();
System.out.println("main() end");
}
static void m1() {
System.out.println("m1() start");
try {
m2();
}
catch (ArithmeticException e) {
System.out.println("m1() 예외처리");
System.out.println(e);
System.out.println(e.getMessage());
e.printStackTrace();
}
System.out.println("m1() end");
}
static void m2() {
System.out.println("m2() start");
System.out.println(1);
try {
System.out.println(2/0);
}
catch(NullPointerException e) {
System.out.println(2.5);
}
finally {
System.out.println(2.7);
}
System.out.println(3);
System.out.println("m2() end");
}
}
package chapter11;
public class ExceptionEx1_4 {
public static void main(String[] args) {
System.out.println("main() start"); // System.err 로 하면 빨강색으로 나옴.
m1();
System.out.println("main() end");
}
static void m1() {
System.out.println("m1() start");
try {
m2();
}
catch (ArithmeticException e) {
System.out.println("m1() 예외처리");
System.out.println(e);
System.out.println(e.getMessage());
e.printStackTrace();
}
System.out.println("m1() end");
}
static void m2() {
System.out.println("m2() start");
System.out.println(1);
try {
// System.out.println(2/0);
throw new ArithmeticException();
}
catch(NullPointerException e) {
System.out.println(2.5);
}
finally {
System.out.println(2.7);
}
System.out.println(3);
System.out.println("m2() end");
}
}
package chapter11;
public class ExceptionEx1_5 {
public static void main(String[] args) {
System.out.println("abcde".substring(10)); // 인덱스 관련 예외처리
m1();
System.out.println("main() end");
}
static void m1() {
System.out.println("m1() start");
try {
m2();
}
catch (ArithmeticException e) {
System.out.println("m1() 예외처리");
System.out.println(e);
System.out.println(e.getMessage());
e.printStackTrace();
}
System.out.println("m1() end");
}
static void m2() {
System.out.println("m2() start");
System.out.println(1);
try {
// System.out.println(2/0);
throw new ArithmeticException();
}
catch(NullPointerException e) {
System.out.println(2.5);
}
finally {
System.out.println(2.7);
}
System.out.println(3);
System.out.println("m2() end");
}
}
11.4 예외 강제 발생
throw라는 키워드를 사용하면 개발자가 직접 예외를 강제로 발생시킬 수 있다. 사용방법은 아래처럼 실행코드를 작성하면 된다.
throw new Exception("예외 발생"); |
package chapter11;
import java.sql.SQLException;
public class Exception2 {
public static void main(String[] args) { // 문법적으로 해결은 했지만 에러가뜸
System.out.println("main start");
m1();
try {
m2();
}
catch (Exception e) {
e.printStackTrace();
}
System.out.println("main end");
}
static void m1() {
SQLException sqlException = new SQLException();
try {
throw sqlException;
}
catch (Exception e) {
e.printStackTrace();
}
}
static void m2() throws Exception{
// throw new Exception("예외 메세지");
// throw new ArithmeticException("메세지");
throw new StackOverflowError("재귀 호출 하지 마라");
//System.out.println(); // return과 동일 throw 이후 못씀
}
}
package chapter11;
public class Exception2_1 {
public static void main(String[] args) {
System.out.println("main start");
m1();
System.out.println("main end");
}
static void m1() throws Exception { // 리턴 타입이라고 볼수있음
throw new Exception(); // 예외 인스턴스
}
}
11.5 예외 떠넘기기
지금까지는 예외를 처리하는 방법으로 try-catch문을 사용했는데, 이 외에 예외를 직접 처리하지 않고 떠넘기는 방법이 있다. 메서드에서 선언하는 방법이다. 메서드에서 예외를 선언하려면, 메서드의 선언부에 throws 키워드를 사용해서 메서드 내에서 발생할 수 있는 예외를 적어주면 된다. 만약 처리해야 할 예외가 여러개라면 ,(콤마)로 구분해서 적어준다.
void 메서드명() throws Exception1, Exception2... { ... } |
throws와 throw가 단어가 비슷해 혼동되지만 별개로 구별해서 기억해야한다. try~catch문과 throws는 예외를 처리하는 구문이고, throw는 예외를 발생시키는 구문이다.
예외 재발생
예외 재발생은 하나의 메서드에서 발생할 수 있는 예외가 여러개인 경우, try~catch문을 통해서도 처리하고, 선언부에서도 선언하여 양쪽에서 처리하도록 하는 것이다. 이러한 방법은 try~catch문으로 예외를 처리한 후 강제로 예외를 다시 발생시키는 방법으로 처리한다.
11.6 사용자 정의 예외 클래스
이미 자바에서는 제공하는 예외 클래스 외에 개발자가 직접 새로운 예외 클래스를 정의해서 사용할 수 있는데, Throwable 클래스나 그 하위 클래스로부터 상속받아 사용자 정의 예외 클래스를 생성한다. 보통 Exception 클래스로부터 상속받아 클래스를 만드는 경우가 많다.
class 클래스명 extends Exception { 클래스명 (String msg) { super(msg); } } |
Exception 클래스를 상속받아 클래스를 정의했다. 추가로 변수나 메서드도 정의할 수 있다. 생성자를 통해 메세지를 입력받아 상위 클래스(Exception)의 생성자로 메세지를 매개변수로 넘겨주며 실행한다.(super())
try-with-resource
java7버전부터 지원하는 기능으로 try() 괄호 안에서 생성한 객체가 AutoCloseable 인터페이스를 구현한 클래스라면 예외 발생 여부에 상관없이 자원 해제가 자동으로 이루어진다. 즉 close() 메서드를 finally 블럭에서 호출하지 않아도 자동으로 호출해 주는 것이다.
기존 try~catch 방식과 try-with resource 방식의 차이점을 비교해보자.
기존 try~catch 방식
FileInputStream is = null;
BefferedInputStream bis = null;
try {
is = new FileInputStream("파일명");
bis = new BufferedInputStream(is);
int data = -1;
while((data = bis.read()) != -1) {
System.out.println((char)data);
}
} finally {
// close 메서드 호출
if (is != null) is.close();
if (bis != null) bis.close();
}
try-with-resource 방식
try (
FileInputStream is = new FileInputStream("파일명");
BefferedInputStream bis = new BufferedInputStream(is);
) {
int data = -1;
while((data = bis.read()) != -1) {
System.out.println((char)data);
}
} catch (IOException e) {
e.printStackTrace();
}
아직 파일스트림은 배우지 않았지만, 파일처리나, DB연결 등 자원을 이용하는 경우 이렇게 위 예제처럼 파일 지원 처리시 try-with-resource 방식을 이용하면 실수로 close() 메서드 호출을 빼먹는 경우도 없어지고, 코드도 짧고 간결해져 유지보수가 수월해지게 된다.
'Full Stack > JAVA' 카테고리의 다른 글
[풀스택과정] JAVA 12장 기본 API (1) | 2023.01.27 |
---|---|
[풀스택과정] JAVA 11장 연습문제 (1) | 2023.01.26 |
[풀스택과정] JAVA 10장 연습문제 (1) | 2023.01.24 |
[풀스택과정] JAVA 10장 내부클래스(중첩클래스) (1) | 2023.01.24 |
[풀스택과정] JAVA 9장 연습문제 (1) | 2023.01.22 |