8.1 예외처리(Exception Handling)
1. 프로그램 오류
프로그램이 실행 중 어떤 원인에 의해서 오작동을 하거나 비정상적으로 종료되는 경우가 있다. 이러한 결과를 초래하는 원인을 프로그램 에러 또는 오류라고 한다.
이를 발생시점에 따라 '컴파일 에러(compile - time error)'와 '런타임 에러(runtime error)'로 나눌 수 있는데, 글자 그대로 '컴파일 에러'는 컴파일 할 때 발생하는 에러이고 프로그램 실행 도중에 발생하는 에러를 '런타임 에러'라고 한다.
이외에도 '논리적 에러(logical error)'가 있는데, 컴파일도 잘되고 실행도 잘되지만 의도한 것과 다르게 동작하는 것을 논리적 에러라고 말한다.
에러 이름 | 에러 발생시점 |
컴파일 에러 | 컴파일 시에 발생하는 에러 |
런타임 에러 | 실행 시에 발생하는 에러 |
논리적 에러 | 실행은 되지만, 의도와 다르게 동작하는 것 |
자바에서는 실행 시(runtime) 발생할수 있는 프로그램 오류를 '에러(Error)'와 '예외(Exception)' 두 가지로 구분하였다.
- 에러는 메모리 부족과 스택오버플로우와 같이 발생하면 복구할 수 없는 심각한 오류이고, 예외는 발생하더라도 수습할 수 있는 비교적 덜 심각한 것이다.
- 에러가 발생하면, 프로그램의 비정상적인 종료를 막을 길이 없지만, 예외는 발생하더라도 프로그래머가 이에 대한 적절한 코드를 미리 작성해 놓음으로써 프로그램의 비정상적인 종료를 막을 수 있다.
2. 예외 클래스의 계층구조
자바에서는 실행 시 발생할 수 있는 오류를 클래스로 정의해놨다. 앞서 배운 것처럼 모든 클래스의 조상은 Object클래스이기 때문에 Exception과 Error 클래스 역시 Object클래스의 자손들이다.
예외 클래스들은 두 그룹으로 나눌 수 있다
▶ Exception클래스와 그 자손들 - 사용자의 실수와 같은 외적인 요인에 발생하는 예외
▶ RuntimeException클래스와 그 자손들 - 프로그래머의 실수로 발생하는 예외
3.예외처리하기 - try - catch문
예외처리란? 프로그램 실행 시 발생할 수 있는 예기치 못한 예외의 발생에 대비한 코드를 작성하는 것이며, 예외처리의 목적은 예외의 발생으로 인한 실행 중인 프로그램의 갑작스런 비정상 종료를 막고, 정상적인 실행상태를 유지할 수 있도록하는 하는 것이다.
- 예외처리(Exception Handling)의
- 정의 - 프로그램 실행 시 발생할 수 있는 예외에 대비한 코드를 작성하는 것
- 목적 - 프로그램의 비정상 종료를 막고, 정상적인 실행상태를 유지하는 것
발생한 예외를 처리하지 못하면, 프로그램은 비정상적으로 종료되며, 처리되지 못한 예외는 JVM의 '예외처리기'가 받아서 예외의 원인을 화면에 출력한다.
try {
//예외가 발생할 가능성이 있는 문장들을 넣는다.
} catch (Exception1 e1) {
//Exception1이 발생했을 경우, 이를 처리하기 위한 문장을 적는다.
} catch (Exception2 e2) {
//Exception2이 발생했을 경우, 이를 처리하기 위한 문장을 적는다.
} catch (Exception3 e3) {
//Exception3이 발생했을 경우, 이를 처리하기 위한 문장을 적는다.
}
public class ExceptionEx1 {
public static void main(String[] args) {
try {
try { } catch (Exception e) { }
} catch(Exception e) {
try { } catch (Exception e) {} //에러 변수 e가 중복 선언되었음
}
try {
} catch (Exception e) {
} //try - catch의 끝
}//main메서드의 끝
}
위의 코드처럼 try-catch문은 하나의 메스드 내에 여러 개의 try-catch문이 사용될 수 있음을 보여준다. 또한 try 블럭 또는 catch블럭에 또다른 try-catch문이 포함될 수 있다.
public class ExceptionEx2 {
public static void main(String arge[]) {
int number =100;
int result =0;
for (int i=0; i < 10; i++) {
result = number/(int)(Math.random()*10);//7번라인
System.out.println(result);
}
}
}
위의 코드는 number에 저장된 값 100을 0~9 사이의 임의의 정수로 나눈 결과를 출력하는 일을 10번 반복하는 코드이다. random()을 사용했기 때문에 매번 실행할 때마다 결과가 다르지만, 대부분의 경우 10번이 출력되기 이전에 예외가 발생해서 프로그램이 비정상적으로 종료될 것이다.
- ArithmeticException - 산술연산과정에서의 오류에 발생하는 오류(정수는 0으로 나누는 것을 금지한다.)
<산술연산 과정에서 오류가 발생하는 것을 예외처리로 둔 코드>
public class ExceptionEx3 {
public static void main(String arge[]) {
int number =100;
int result =0;
for (int i=0; i < 10; i++) {
try {
result = number/(int)(Math.random()*10);//7번라인
System.out.println(result);
} catch(ArithmeticException e) {
System.out.println("0");
}
}
}
}
100
50
11
20
25
16
33
11
0 --> 예외 발생상황이지만 예외처리로 0이 출력 됨.
11
*또한 try {} catch{}문에서는 괄호 생략이 불가하다.
4. try - catch문에서의 흐름
try - catch문에서, 예외가 발생한 경우와 발생하지 않았을 때의 흐름(문장의 실행순서)이 달라지는데, 아래에 이 두 가지 경우에 따른 문장 실행순서를 정리하였다.
- try 블럭 내에서 예외가 발생한 경우
- 발생한 예외와 일치하는 catch블럭이 있는지 확인한다
- 일치하는 catch 블럭을 찾게 되면, 그 catch 블랙 내의 문장들을 수행하고 전체 try - catch문을 빠져나가서 그 다음 문장을 계속해서 수행한다. 만일 일치하는 catch블럭을 찾지 못하면, 예외는 처리되지 못한다. (프로그램 비정상 종료)
- try 블럭 내에서 예외가 발생하지 않은 경우
- catch 블럭을 거치지 않고 전체 try - catch문을 빠져나가 수행을 계속한다.
public class Java8_4_1 {
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) {
if (ae instanceof ArithmeticException)
System.out.println("True");
System.out.println("ArithmeticException");
} catch(Exception e) { //모든 예외를 처리
System.out.println("Exception");
}
System.out.println(6);
}
}
<실행 결과>
1
2
3
True
ArithmeticException
6
< 예외처리를 안 해서 비정상적인 종료를 하는 코드 >
//예외처리가 안 되어서 비정상적으로 종료가 됨.
public class Java8_4_2 {
public static void main(String args[]) {
System.out.println(1);
System.out.println(2);
try {
System.out.println(3);
System.out.println(args[0]);
System.out.println(4);
} catch (ArithmeticException ae) {
if (ae instanceof ArithmeticException)
System.out.println("True");
System.out.println("ArithmeticException");
}
System.out.println(6);
}
}
<예외 발생에 정상적인 예외 처리를 진행하여 정상 종료 하는 코드>
//예외가 발생했지만 처리가 되었기에 정상종료임
public class Java8_4_3 {
public static void main(String args[]) {
System.out.println(1);
System.out.println(2);
try {
System.out.println(3);
System.out.println(args[0]); // ArrayIndexOutOfBoundsException 에러
System.out.println(4); // 실행되지 않는다.
} catch (ArithmeticException ae) {
if (ae instanceof ArithmeticException)
System.out.println("True");
System.out.println("ArithmeticException");
} catch(ArrayIndexOutOfBoundsException e) {
System.out.println("ArrayIndexOutOfBoundsException");
}
System.out.println(6);
}
}
5. 예외의 발생과 catch블럭
- 내용이 같은 catch블럭을 하나로 합친 것
부모자식 관계에서는 멀티 catch블럭 말고 저 위의 그림처럼 사용해야 한다.
- 멀티 catch 사용 시 공통 멤버만 사용해야 한다. 어떤 객체를 가르킬지 모르는 상황에서 한 객체 안에 있는 메서드를 사용하는 것은 불가.
- 정 사용하고 싶다면 아래 if문을 참고해서 사용해야 한다.
6. 예외 발생시키기
- 연산자 new를 이용해서 발생시키려는 예외 클래스의 객체를 만든다.
- Exception e = new Exception("고의로 발생시켰음");
- 키워드 throw를 이용해서 예외를 발생시킨다.
- throw e;
public class Java8_6_1 {
public static void main(String[] args) {
// TODO 자동 생성된 메소드 스텁
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 javajungsuk.Java8_6_1.main(Java8_6_1.java:9)
👉 try문에서 던진 예외를 catch 블럭에서 잡았기 때문에 정상 종료 되었다.
7. 메서드에 예외 선언하기
- printStackTrace( ) - 예외 발생 당시의 호출스택(Call Stack)에 있던 메서드의 정보와 예외 메시지를 화면에 출력한다.
- getMessage( ) - 발생한 예외클래스의 인스턴스에 저장된 메시지를 얻을 수 있다.
이 두개 이외에도 다른 메서드가 있지만 제일 많이 사용되어 이 두개를 소개해줌.
참조변수가 예외 객체를 가르킨다. (ArithmeticException ae) --: ae 참조변수가 예외 객체를 가르킴.
8. final 블럭
- 예외 발생여부와 관계없이 수행되어야 하는 코드를 넣는다.
- try - catch문의 마지막에 finally 코드를 넣는다.
- 임시파일 삭제와 같이 매 코드 아래의 수행되어야 하는 코드 같은 경우엔 finally 블럭을 넣어 코드 중복을 제거해준다.
9. 자동 자원 반환 - try - with - resources문
10. 사용자정의 예외 만들기
- 우리가 직접 예외 클래스를 정의할 수 있다.
- 조상은 Exception과 RuntimeException 중에서 선택할 수 있다.
- Exception - 사용자가 발생시키는 예외
- 예외 처리를 필수로 해야하기 때문에 try - catch문을 사용해야 한다.
- RuntimeException - 프로그래머의 실수로 발생시키키는 예외
- 예외 처리를 선택적으로 할 수 있기 때문에 try - catch문을 선택해서 사용하면 된다.
- 또한 에외 처리를 선택적으로 할 수 있기에 조상을 RuntimeException을 선택하는 것이 좋다.
- Exception - 사용자가 발생시키는 예외
11. 예외 되던지기(exception re-throwing)
- 예외를 처리한 후에 다시 예외를 발생시키는 것
- 호출한 메서드와 호출된 메서드 양쪽 모두에서 예외처리하는 것
//예외 되던지기
public class Java8_11_2 {
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; //다시 예외를 발생시킨다.
}
}
}
12. 연결된 예외(chained exeption)
- 한 예외가 다른 예외를 발생시킬 수 있다
- 예외 A가 예외 B를 발생시키면 A는 B의 원인 예외(cause exception)다
이때 A와 B를 연결된 예외라고 한다.
<연결된 예외란? >
- 예외 발생
- catch에서 예외처리를 안 하고 새로운 예외를 만듬
- 만든 예외(B) 안에 발생한 예외(A)를 넣어서 다시 되던짐
- 이때 B안에 A를 넣고 되던진 예외를 우리는 연결된 예외라고 한다.
< 연결된 예외를 쓰는 이유는? >
1.여러 예외를 하나로 묶어서 다루기 위해서
- 예외가 많아지면 catch블럭이 수없이 많아지기 때문에 이를 해결하기 위해서 하나로 묶어 사용한다.
public class Java8_12_3 {
try {
startInstall();
copyFiles();
} catch(SpaceException e) {
InstallExcption ie = new InstallException("설치 중 예외발생"); // 예외 발생
ie.initCause(e); //InstallExcption의 원인 예외를 SpaceException으로 저장
Throw ie;
} catch(MemoryException me) {
...
}
}
원인 예외를 저장해서 사용하는 방법
2. checked 예외를 un checked 예외로 변경하려 할 때
- 필수처리를 선택처리로 바꾸려 할 때 사용한다.
13. checked예외, unchecked예외
- checked - 컴파일러가 예외 처리 여부를 체크(예외 처리 필수 try - catch 필수)
- Exception( )
- unchecked - 컴파일러가 예외 처리 여부를 체크 안 함(예외 처리 선택 try - catch 선택)
- RuntimeException
import javax.management.RuntimeErrorException;
public class Java8_14_1 {
public static void main(String[] args) {
try { //Exception과 그 자손은 반드시 예외처리를 해줘야 한다.
throw new Exception();
} catch(Exception e) {}
//RuntimeException과 그 자손은 예외처리를 해주지 않아도 컴파일 된다.
throw new RuntimeException();
}
}
14. 오류 (Error)
- 컴파일 에러
- 런타임 에러
- Error - 심각
- Exception - 덜 심각
- Exception과 자손 : checked 예외
- RuntimeException과 자손 : Unchecked 예외
- 논리적 에러
15. 메서드에 예외 선언하기
- try - catch
- 예외 선언하기 -> 예외 떠넘기기(알리기) throws / 예외 발생 - throw
- 은폐 덮기 (빈 catch 블럭)
-> 폭탄 돌리기마냥 예외를 처리하지 않고 떠넘긴 뒤 마지막에 JVM에서 처리 한다.
< 메서드에 예외 선언하기 >
import java.io.File;
//파일 생성에 실패한 코드
public class Java8_11_1 {
public static void main(String[] args) {
try {
File f = createFile("");
System.out.println(f.getName()+" 파일이 성공적으로 생성되었습니다.");
} catch(Exception e) {
System.out.println(e.getMessage()+" 다시 입력해 주시기 바랍니다.");
}
}//main 메서드 끝
static File createFile(String fileName) throws Exception {
if (fileName == null || fileName.equals(""))
throw new Exception("파일이름이 유효하지 않습니다.");
File f = new File(fileName); //File클래스 객체를 만든다
f.createNewFile();
return f; //생성된 객체의 참조를 반환한다.
}
}
import java.io.File;
//파일 생성에 성공한 코드
public class Java8_11_1 {
public static void main(String[] args) {
try {
File f = createFile("test2.txt");
System.out.println(f.getName()+" 파일이 성공적으로 생성되었습니다.");
} catch(Exception e) {
System.out.println(e.getMessage()+" 다시 입력해 주시기 바랍니다.");
}
}//main 메서드 끝
static File createFile(String fileName) throws Exception {
if (fileName == null || fileName.equals(""))
throw new Exception("파일이름이 유효하지 않습니다.");
File f = new File(fileName); //File클래스 객체를 만든다
f.createNewFile();
return f; //생성된 객체의 참조를 반환한다.
}
}
< 제목 없음 파일이 성공적으로 생성되었습니다 코드 >
import java.io.File;
public class Java8_11_1 {
public static void main(String[] args) {
try {
File f = createFile("");
System.out.println(f.getName()+" 파일이 성공적으로 생성되었습니다.");
} catch(Exception e) {
System.out.println(e.getMessage()+" 다시 입력해 주시기 바랍니다.");
}
}//main 메서드 끝
static File createFile(String fileName) throws Exception {
try {
if (fileName == null || fileName.equals(""))
throw new Exception("파일이름이 유효하지 않습니다.");
}catch(Exception e) {
fileName = "제목 없음";
}
File f = new File(fileName); //File클래스 객체를 만든다
f.createNewFile();
return f; //생성된 객체의 참조를 반환한다.
}
}
'Java > 객체지향' 카테고리의 다른 글
[객체지향][자바의 정석] - 9.2 String 클래스 (0) | 2022.01.08 |
---|---|
[객체지향][자바의 정석] - 9.1 Object 클래스 (0) | 2021.12.28 |
[객체지향][자바의 정석] - 7.8 내부 클래스(Inner class) (0) | 2021.12.14 |
객체지향 개념 끝 (0) | 2021.12.14 |
[객체지향][자바의 정석] - 7.7 인터페이스(Interface) (0) | 2021.12.03 |