일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- union find
- 이진탐색
- Two Points
- DP
- 다익스트라
- SQL
- two pointer
- 그래프
- Trie
- Brute Force
- Stored Procedure
- 스토어드 프로시저
- Hash
- String
- Dijkstra
- binary search
- MYSQL
- Today
- Total
codingfarm
예외 처리(exception handling) 본문
1. 프로그램 오류
- 에러는 프로그램의 오작동, 비정상적인 종료를 야기한다.
- 컴파일 에러 : 컴파일 시 발생하는 에러, 자바 컴파일러가 감지한다.
- 런타임 에러 : 프로그램의 실행도중 발생하는 에러, JVM이 감지한다.
- JAVA에서는 런타임 에러를 에러(error)와 예외(exception) 두 가지로 구분하였다.
- 에러
- 메모리 부족(OtOfMemoryError)나 스택오버플로우(StackOverflowError) 처럼 발생하면 복구할 수 없는 심각한 오류
- 비정상적인 종료를 막을 수 없다.
- 예외
- 발생하더라도 수습가능한 비교적 덜 심각한 오류
- 이를 대비할 수 있는 코드를 미리 작성하여 비정상적인 오류를 막을 수 있다.
- 에러
※ 컴파일 에러도 예외처리가 가능하다.
2. 예외처리의 정의와 목적
- 정의 : 프로그램 실행 시 발생할 수 있는 예외의 발생에 대비한 코드를 작성하는 것
- 목적 : 프로그램의 비정상 종료를 막고, 정상적인 실행상태를 유지하는 것
3. 예외 처리 구문 : try-catch
- 구조
1
2
3
4
5
6
7
8
9
10
|
try {
// 예외가 발생할 가능성이 있는 문장들을 넣는다.
} catch (Exception1 e1) {
// Exception1이 발생했을 경우, 이를 처리하기 위한 문장을 적는다.
} catch (Exception2 e2) {
// Exception2가 발생했을 경우, 이를 처리하기 위한 문장을 적는다.
...
} catch (ExceptionN eN) {
// ExceptionN이 발생했을 경우, 이를 처리하기 위한 문장을 적는다.
}
|
cs |
- 하나의 try블럭 다음에는 여러 종류의 예외를 처리할 수 있도록 하나 이상의 catch 블럭이 올 수 있다.
- 여러개의 catch 블럭 중 예외의 종류와 일치하는 단 한개의 블럭만 수행된다.
- 예외의 종류와 일치하는 catch 블럭이 없으면 예외처리는 되지 않는다.
※ if문과 달리, try와 catch 블럭은 문장이 하나라고 괄호{}를 생략할 수 없다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public class Hello {
public static void main(String[] args) {
try {
try { } catch (Exception e) { }
} catch (Exception e) {
try { } catch (Exception e) { } // Compile Error
}
try {
} catch (Exception e) {
}
}
}
|
cs |
- 같은
- 5번째와 6번째 줄의 참조 변수 영역이 서로 겹치기 때문에 에러가 발생한다.
- 실제 사용 예
1
2
3
4
5
6
7
8
9
10
11
|
public class Hello {
public static void main(String[] args) {
int number = 100;
int result = 0;
for(int i=0; i<10; i++) {
result = number / (int) (Math.random() * 10);
System.out.println(result);
}
}
}
|
cs |
위 예제는 number에 랜덤한 값을 나누는 수행을 반복한다.
이 때 random 함수가 우연히 0을 반환하게 되면 예외가 발생하여 프로그램이 비정상적으로 종료될 것이다.
예외가 발생하는 원인을 알았으니, 예외처리 구문을 통해 프로그램의 비정상적인 종료를 막아보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public class Hello {
public static void main(String[] args) {
int number = 100;
int result = 0;
for(int i=0; i<2; i++) {
try {
result = number / (int) (Math.random() * 10);
System.out.println(result);
} catch (ArithmeticException e) {
System.out.println("0");
}
}
System.out.println(5/0);
}
}
|
cs |
예외 처리를 통해 0으로 나누려 시도하는 ArithmetricException이 발생하면 0을 화면에 출력하게끔 했다.
4. try-catch 문에서의 흐름
- 예외가 발생한 경우와 발생하지 않았을 때의 흐름은 서로 다르다.
- try 블럭내에서 예외 발생시
- 발생한 예외와 일치하는 catch 블럭이 있는지 확인한다.
- 일치하는 catch 블럭을 찾으면, 블럭 내의 문장들을 수행하고, 전체 try-catch 문을 빠져나가서 다음 문장을 계속 수행한다.
- 일치하는 catch 블럭을 찾지 못하면, 예외는 처리되지 못하고, 프로그램은 비정상 종료된다.
- try 블럭 내에서 예외가 발생하지 않은 경우
- catch 블럭을 거치지 않고 전체 try-catch 문을 빠져나가서 수행을 계속한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public class Hello {
public static void main(String[] args) {
System.out.println(1);
System.out.println(2);
try {
System.out.println(3);
System.out.println(4);
} catch(Exception e) {
System.out.println(5);
} // try-catch의 끝
System.out.println(6);
} // main 메서드의 끝
}
|
cs |
- 그 어떠한 예외도 발생하지 않앗으므로 catch 블럭 내의 출력문은 호출되지 않는다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public class Hello {
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) {
System.out.println(5);
} // try-catch의 끝
System.out.println(6);
} // main 메서드의 끝
}
|
cs |
- 0/0 을 나누는 연산에서 ArithmeticException이 발생, 그 이후의 9번째 줄의 문장은 무시되고 곧바로 catch 블럭내의 문장이 출력된다.
- 예외가 발생하면 try블럭 내에서 그 이후의 문장은 출력되지 않는것을 볼 수 있다. 그러므로 try의 범위는 잘 설정 해주어야 한다.
5. 예외 발생시키기
- 키워드 throw를 사용하여 프로그래머가 고의로 예외를 발생시킬 수 있다.
1. 연산자 new를 이용해서 발생시키려는 예외 클래스의 객체를 만든다.
Exception e = new Exception("고의로 발생시켰음");
2. 키워드 throw를 이용해서 예외를 발생시킨다.
throw e;
아래처럼 한줄로 예외를 발생시킬 수도 있다.
throw new Exception();
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public class Hello {
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("프로그램이 정상 종료되었음.");
}
}
|
cs |
6. 예외 클래스의 계층 구조
- JAVA에서는 실행시 발생할 수 있는 오류(Exception과 Error)를 클래스로 정의하였다.
- 조상은 Object 클래스이다. 즉, Exception과 Error 클래스 역시 Object 클래스의 자손이다.
- 모든 예외의 최고 조상은 Exception 클래스이며, 에러의 최고 조상은 Error 클래스 이다.
- Exception과 Error 클래스들은 Throwable을 공통조상으로 가진다.
- 예외 클래스들은 2개의 그룹으로 나뉠 수 있다.
- RuntimeException클래스와 그 자손클래스들
- RuntimeException 클래스들 이라 지칭
- 프로그래머의 실수에 의해 주로 발생
- 배열의 범위를 벗어날 경우(IndexOutOfBoundsException)
- null인 참조변수의 멤버를 호출(NullPointerException)
- 클래스간의 형변환 실수(ClassCastException)
- 0으로 나누려 함(ArithmeticException)
- Exception클래스와 그 자손클래스들 (RuntimeException과 자손들 제외)
- Exception 클래스들 이라 지칭
- 외부의 영향, 주로 프로그램의 사용자들의 동작에 의해 주로 발생
- 존재하지 않는 파일의 이름을 입력(FileNotFoundException)
- 클래스의 이름을 잘못 입력(ClassNotFoundException)
- 입력한 데이터 형식이 잘못된 경우(DataFormatException
- RuntimeException클래스와 그 자손클래스들
- RuntimeException클래스들과 Exception클래스들의 차이점 = >컴파일시의 예외처리 체크 여부
- RuntimeException 클래스들 그룹에 속하는 예외가 발생할 가능성이 있는 코드에는 예외 처리를 해주지 않아도 컴파일 시에 문제가 되지 않는다.
- Exception클래스들 그룹에 속하는 예외가 발생할 가능성이 있는 예외는 처리하지 않으면 컴파일 에러가 발생한다.
※ RuntimeException 클래스들에 속하는 예외가 발생할 가능성이 있는 코드에도 예외처리를 해야 한다면, 모든 참조변수와 배열이 사용되는 곳에 예외처리를 해주어야 한다.
1
2
3
4
5
|
public class Hello {
public static void main(String[] args) {
throw new Exception(); // Exception을 강제로 발생 시킨다.
}
}
|
cs |
- 위 코드는 컴파일 에러가 발생한다.
1
2
3
4
5
6
7
8
9
|
public class Hello {
public static void main(String[] args) {
try {
throw new Exception(); // Exception을 강제로 발생 시킨다.
} catch(Exception e) {
System.out.println("Exception이 발생했습니다.");
}
} // main 메서드의 끝
}
|
cs |
- 예외 처리를 통해 컴파일과 실행을 정상적으로 수행하였다.
1
2
3
4
5
|
public class Hello {
public static void main(String[] args) {
throw new RuntimeException(); // RuntimeException을 강제로 발생시킨다.
}
}
|
cs |
- catch가 없어도 컴파일은 되지만, 런타임시에 에러가 발생함을 볼 수 있다.
RuntimeException 클래스들은 예외처리를 해주지 않아도 컴파일러가 문제삼지 않는다.
7. 예외 발생과 catch 블럭
- catch 블럭은 괄호()와 블럭{}으로 나뉘어져있다.
- 괄호() : 처리하고자 하는 예외와 같은 타입의 참조변수 하나를 선언
- 블럭{} : 예외를 catch 했을때 호출해야할 명령문들
- 예외가 발생하면
- 발생한 예외에 해당하는 클래스의 인스턴스가 만들어진다.
- 예외가 발생한 문장이 try블럭에 포함되어 있다면, 이 예외를 처리할 수 있는 catch 블럭이 있는지 찾게 된다.
- 검사결과가 true인(예외를 처리할 수 있는) catch 블럭을 찾게 되면 블럭에 있는 문장들을 모두 수행한 후에 try-catch문을 빠져나오고 예외는 처리된다.
- 검사결과가 true인 catch 블럭이 하나도 없으면 예외는 처리되지 않는다.
- 모든 예외 클래스는 Exception 클래스의 자손이므로, catch 블럭의 괄호()에 Exception 클래스 타입의 참조 변수를 선언해 놓으면 어떤 종류의 예외가 발생하더라도 이 catch 블럭에 의해서 처리된다.
- try-catch문의 마지막에 Exception클래스 타입의 참조변수를 선언한 catch 블럭을 사용하면, 어떤 종류의 예외에 대해서도 이 catch블럭에 의해 처리되도록 할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public class Hello {
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 (Exception e) {
System.out.println(5);
}
System.out.println(6);
}
}
|
cs |
- 0/0 에 대해 ArithmeticException이 발생하지만 이의 조상 클래스인 Exception의 참조변수가 있어도 예외를 받을 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public class Hello {
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);
}
}
|
cs |
- 예외가 발생했을 때 생성되는 예외클래스의 인스턴스에는 발생한 예외에 대한 정보가 담겨져 있으며, getMessage()와 printStackTrace()를 통해서 이 정보들을 얻을 수 있다.
- catch 블럭의 괄호()에 선언된 참조변수를 통해 이 인스턴스에 접근할 수 있다.
- 이 참조변수는 선언된 catch 블럭 내에서만 사용가능한다.
- 주로쓰이는 메서드는 아래와 같다.
- printStackTrace() : 예외 발생시 출력되어야할 예외메시지를 출력한다.
- printStackTrace(PrintStream s) , printStackTrace(PrintWriter s) : 발생한 예외에 대한 정보를 파일에 저장한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
import java.io.*;
public class Hello {
public static void main(String[] args) {
PrintStream ps = null;
try {
ps = new PrintStream("error.log"); // 파일 error.log에 출력할 준비를 한다.
System.out.println(1);
System.out.println(2);
System.out.println(3);
System.out.println(0/0); // 예외 발생
System.out.println(4); // 실행 되지 않는다.
} catch (Exception ae) {
ae.printStackTrace(ps);
ps.println("예외메시지 : " + ae.getMessage()); // 화면 대신 error.log 파일에 출력 한다.
}
System.out.println(6);
}
}
|
cs |
프로젝트 경로 내에 error 파일이 생성되고, 출력문이 저장되었음을 볼 수 있다.
8. finally 블럭
- try-catch문과 함께, 예외의 발생 여부에 상관없이 실행되어야할 코드를 포함시키는데 쓰인다.
1
2
3
4
5
6
7
8
|
try {
// 예외가 발생할 가능성이 있는 코드 작성
} catch (Exception1 e1) {
// 예외처리를 위한 문장을 적는다.
} finally {
// 예외의 발생여부에 관계없이 항상 수행되어아하는 문장들을 넣는다.
// finally 블럭은 try-catch문의 맨 마지막에 위치해야한다.
}
|
cs |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
package project_1;
public class Hello {
public static void main(String[] args) {
Hello.method1();
System.out.println("method1()의 수행을 마치고 main메서드로 복귀했습니다.");
}
static void method1() {
try {
System.out.println("method1() 이 호출되었습니다.");
return;
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("method1()의 finally 블럭이 실행되었습니다.");
}
}
}
|
cs |
- 위 코드의 결과에서 알 수 있듯이, try블럭에서 return문이 실행되는 경우에도 finally 블럭의 문장들이 먼저 실행된 후에, 현재 실행 중인 메서드를 종료한다.
- 이와 마찬가지로 catch블럭의 문장 수행 중에 return문을 만나도 finally 블럭의 문장들은 수행된다.
9. 메서드에 예외 선언하기
- 예외 처리를 위해 지금까지는 try-catch 문을 사용했다.
- 이외에 예외를 메서드에 선언하는 방법도 있다.
- 메서드의 선언부에 키워드 throws를 사용해서 메서드 내에서 발생할 수 있는 예외를 적어주면 된다.
- 예외가 여러개면 쉼표로 구분한다.
1
2
3
|
void method() throws Exception1, Exception2, ... ExceptionN {
// 메서드의 내용
}
|
cs |
- JAVA에서는 이와 같은 방법을 통해 메서드를 사용하려는 사람이 메서드의 선언부를 보았을 때, 이 메서드를 사용하기 위해서는 어떠한 예외들이 처리되어져야 하는지 쉽게 알 수 있다.
- 그 자체로써 예외를 처리하는 기능을 가진게 아니라, 자신을 호출한 메서드에게 예외를 전달하여 예외처리를 떠맡기는 것이다.
JAVA API문서에서 java.lang.Object클래스의 wait 메서드를 살펴보자
docs.oracle.com/javase/7/docs/api/index.html
위 문서를 통해 wait 메서드에서 발생하는 예외들에 대한 정보를 볼 수 있으며, 언제 예외가 발생하는지 설명이 덧붙여저 있으므로, 이 메서드를 호출하고자 하는 메서드에서는 미리 예외에 대한 대응을 할 수 있다.
InterrupedException 링크를 통해 들어가 해당 예외에 대한 정보를 확인해본다.
위 정보를 통해 InterruptedException은 Exception 클래스의 자손임을 알 수 있다. 따라서 반드시 처리해주어야 하는 예외임을 알 수 있다. 그러므로 wait 함수의 선언부에서는 throws 키워드와 함게 선언되어진것을 볼 수 있다.
그러면 나머지 2개의 예외는 어떨까?
RuntimeException클래스의 자손이므로 예외처리를 할 필요가 없다. 그래서 wait 메서드의 선언부에 IllegalMonotirStateException과 IllegalArgumentException은 적혀있지 않다.
메서드에 예외를 선언할 때 일반적으로 RuntimeException클래스들은 적지 않는다.
함수의 선언부에는 보통 반드시 처리해주어야 하는 예외들만 선언한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
package project_1;
public class Hello {
public static void main(String[] args) throws Exception {
method1();
}
static void method1() throws Exception {
method2();
}
static void method2() throws Exception {
throw new Exception();
}
}
|
cs |
- method2에서 예외를 throw했지만 main과 method1, method2 중 어떤 함수에서도 이를 catch 하지 않아 에러가 발생했다.
- 3개의 함수 중 아무 한군대에서라도 catch를 했다면 비정상종료는 발생하지않는다.
- 프로그램 실행 도중 java.lang.Exception이 발생하여 비정상적으로 종료됐다는 것과 예외가 발생했을 때 호출스택(Call Stack)의 내용을 알 수 있다.
- 위 출력을 통해 다음과 같은 사실을 알 수 있다.
$\cdot$ 예외 발생시, 3개의 메서드(main, method1, method2)가 호출 스택에 있었다.
$\cdot$ 예외가 발생한 곳은 제일 윗줄의 method2이다.
$\cdot$ main메서드가 method1()를, 그리고 method1()은 method2()를 호출했다는 것을 알 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
package project_1;
public class Hello {
public static void main(String[] args) throws Exception {
method1();
}
static void method1() throws Exception {
try {
method2();
} catch(Exception e) {
System.out.println("method1 : catch the exception");
e.printStackTrace();
}
}
static void method2() throws Exception {
throw new Exception();
}
}
|
cs |
예외처리를 method1에서 catch 하여 비정상적인 종료를 막았다.
printStackTrace를 통해 함수 콜스택을 확인할 수 있다.
예외 처리 팁
예외를처리하는 방법은 크게 2가지로 나뉜다.
- 예외가 발생한 메서드 내에서 예외를 처리한다.
- 예외가 발생한 메서드에서 처리하지 않고 호출한 메서드로 넘긴다.
혹은 두 메서드가 예외처리를 분담할수도 있다.
첫번째 사례의 예를 보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
import java.io.*;
class ExceptionEX1 {
public static void main(String[] args) throws Exception {
File f = createFile(args[0]);
System.out.println(f.getName() + " 파일이 성공적으로 생성되었습니다.");
}
static File createFile(String fileName) {
try {
if(fileName == null || fileName.equals(""))
throw new Exception("파일이름이 유효하지 않습니다.");
} catch (Exception e) {
// fileName이 부적절한 경우, 파일 이름을 '제목없음.txt'로 한다.
fileName = "제목없음.txt";
} finally {
File f = new File(fileName);
createNewFile(f);
return f;
}
}
static void createNewFile(File f) {
try {
f.createNewFile();
} catch(Exception e) {}
}
}
|
cs |
- 생성할 파일의 이름을 main의 매개변수로 전달받음
- createFile 함수에서 파일 이름을 조사
- 이름이 비어있다 : 예외로 던진후 파일이름을 "제목없음.txt"로 설정
- 파일이름을 바탕으로 파일 생성
예외가 발생한 메서드에서 직접 예외를 처리하도록 되어있다.
createFile메서드를 호출한 main 메서드에서는 예외가 발생한 사실을 알지 못한다.
이 경우 main함수의 작성자가 옳지못한 파일명을 전달하였음에도 자신의 의도와 맞지 않는 파일을 생성받게된다.
두번째 사례를 보자
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
import java.io.*;
class ExceptionEX2 {
public static void main(String[] args) throws Exception {
try {
File f = createFile(args[0]);
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);
f.createNewFile();
return f;
}
}
|
cs |
이전 예제와는 달리 createFile을 호출한 main에서 직접 예외를 처리하도록 하였다.
그 결과 main의 작성자가 직접 예외에 대해 원하는 처리를 할 수 있게 되었다.
예외에 대한 처리를 함수의 호출자에게 맡기고 싶다면 해당 함수를 호출한 함수에서 예외를 처리하도록 유도한다.
'Programming Language > JAVA' 카테고리의 다른 글
사용자정의 예외 만들기 (0) | 2021.04.05 |
---|---|
예외 되던지기(exception re-throwing (0) | 2021.04.05 |
인터페이스(interface) (0) | 2021.03.11 |
추상클래스(abstract class) (0) | 2021.03.11 |
다형성(polymorphism) (0) | 2021.03.10 |