이 글은 Java의 정석 (남궁성/도우출판) 기반으로 작성되었습니다.
1. 프로그램 오류
컴파일 에러(compile-time error): 컴파일할 때 발생하는 에러
런타임 에러(runtime error): 실행할 때 발생하는 에러
- 에러(error) : 프로그램 코드에 의해서 수습될 수 없는 심각한 오류
- 예외(exception) : 프로그램 코드에 의해서 수습될 수 있는 다소 미약한 오류 (처리 가능한 오류)
2. 예외처리의 정의와 목적
에러(error)는 어쩔 수 없지만, 예외(exception)는 처리해야 합니다.
예외처리(exception handling)는 프로그램 실행 시 발생할 수 있는 예외의 발생에 대한 코드를 작성하는 것입니다.
예외처리의 목적은 프로그램의 비정상 종료를 막고, 정상적인 실행상태를 유지하는 것입니다.
(에러와 예외는 모두 실행 시(runtime) 발생하는 오류입니다.)
3. 예외처리구문 : try-catch
예외를 처리하려면 try-catch문을 사용해야 합니다.
try {
} catch (Exception e) {
// 왠만하면 catch 구문 안에는 logic을 넣지 않고, 보통 log만 찍는다.
// if문과 달리 try 블럭이나 catch블럭 내에 포함된 문장이 하나라고 해서 괄호{}를 생략할 수는 없다.
} catch (Exception e2) {
// catch 구문은 많이 쓸 수 있다.
// catch계층구조가 높을수록 아래로 내려가야 한다.
// 자식이 처리할 수 있는건 자식이 하고 처리하지 못하는 것은 아래에 둔다. (부모가 처리)
// 순서가 바뀌면 UnReachableException이 발생한다.
} catch (Exception ignored) {} // exception을 무시할때 ignored로 표기한다.
4. try-catch문에서의 흐름
try블럭 내에서 예외가 발생한 경우,
1. 발생한 예외와 일치하는 catch블럭이 있는지 확인합니다.
2. 일치하는 catch블럭을 찾게 되면, 그 catch블럭 내의 문장들을 수행하고 전체 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); // 오류 발생
System.out.println(4);
} catch (ArithmeticException ae) {
System.out.println(5);
}
System.out.println(6);
}
}
[실행결과]
1
2
3
5
6
try블럭 내에서 예외가 발생하지 않은 경우,
1. catch블럭을 거치지 않고 전체 try-catch문을 빠져나가서 수행을 계속합니다.
class ExceptionEx4 {
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);
}
System.out.println(6);
}
}
[실행결과]
1
2
3
6
5. 예외 발생시키기
먼저, 연산자 new를 이용해서 발생시키려는 예외 클래스의 객체를 만든 다음
Exception e = new Exception(...);
키워드 throw를 이용해서 예외를 발생시킵니다.
throw e;
class ExceptionEx6 {
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 ExceptionEx6.main(ExceptionEx6.java:4)
프로그램이 정상 종료되었음.
여기서 잠깐! throw와 throws의 차이를 아는가?
throws는 메소드나 생성자가 수행할 때 발생하는 exception을 선언할 때 사용하고, throw는 강제로 예외를 발생시키는 경우에 사용합니다.
public void doException() throws Exception {
...
if (...) {
throw new Exception();
}
...
}
6. 예외 클래스의 계층구조
RuntimeException (Unchecked Exception)
- 컴파일하는 데는 문제가 없지만 code적인 부분의 에러 임으로 실행하면 문제가 발생합니다.
예) RuntimeException클래스들 중의 하나인 ArithmeticException을 try-catch문으로 처리하는 경우도 있지만, 사실 try-catch문을 사용하기보다는 0으로 나누지 않도록 프로그램을 변경하는 것이 올바른 처리방법입니다.
Exception (Checked Exception)
- Exception 처리 코드 여부를 compiler가 check 합니다.
예) 사용자가 존재하지 않는 파일을 처리하려 한다던지 (FileNotFoundException), 입력한 데이터의 형식이 잘못되었다던가 (DataFormatException)하는 경우에는 반드시 처리를 해주어야 합니다.
checked Exception | Unchecked Exception | |
처리 여부 | 반드시 예외를 처리해야 함 | 명시적인 처리를 강제하지 않음 |
확인 시점 | 컴파일 단계 | 실행단계 |
예외 발생 시 트랜잭션 처리 | roll-back 하지 않음 | roll-back 함 |
대표 예외 | Exception의 상속받는 하위 클래스 중 RuntimeException을 제외한 모든 예외 - IOException - SQLException | RuntimeException 하위 예외 - NullPointerException - IllegalArgumentException - IndexOutOfBoundException - ArithmeticException |
7. 예외의 발생과 catch블럭
try블럭에서 예외가 발생하면, 발생한 예외를 처리할 catch블럭을 찾습니다.
첫 번째 catch블럭부터 순서대로 찾아 내려가며, 일치하는 catch블럭이 없으면 예외는 처리되지 않습니다.
예외의 최고 조상인 Exception을 처리하는 catch블록은 모든 종류의 예외를 처리할 수 있습니다. (반드시 마지막 catch블럭이어야 합니다.)
class ExceptionEx11 {
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 발생
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"); // ArithmeticException을 제외한 모든 예외가 처리된다.
}
System.out.println(6);
}
}
[실행결과]
1
2
3
true
ArithmeticException
6
발생한 예외 객체를 catch블럭의 참조 변수로 접근할 수 있습니다.
- printStackTrace() - 예외 발생 당시의 호출 스택(Call Stack)에 있었던 메서드의 정보와 예외 메시지를 화면에 출력합니다.
- getMessage() - 발생한 예외 클래스의 인스턴스에 저장된 메시지를 얻을 수 있습니다.
class ExceptionEx11 {
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) {
ae.printStackTrace();
System.out.println("예외메시지 : " + ae.getMessage());
}
System.out.println(6);
}
}
[실행결과]
1
2
3
java.lang.ArithmeticException: / by zero
at ExceptionEx11.main(ExceptionEx6.java:17)
예외메시지 : / by zero
6
개인적으로 추천하지 않는 방식입니다.
개발할 때에는 에러 내용을 보기 위해 사용할 수 있지만, 실제 운영 시에는 catch블럭에 에러가 발생했다는 log만 찍히는 것이 좋습니다.
8. finally 블럭
예외 발생 여부와 관계없이 실행되어야 하는 코드를 넣는습니다.
선택적으로 사용할 수 있으며, 예외 발생 시, try-catch-finally의 순서로 실행되고, 예외 미발생시, try-finally의 순서로 실행됩니다.
try 또는 catch블럭에서 return문을 만나도 finally 블럭은 수행됩니다.
class FinallyTest {
public static void main(String[] args) {
try {
startInstall(); // 프로그램 설치에 필요한 준비를 한다.
copyFiles(); // 파일들을 복사한다.
deleteTempFiles(); // 프로그램 설치에 사용된 임시파일들을 삭제한다.
} catch (Exception e) {
e.printStackTrace();
deleteTempFiles(); // 프로그램 설치에 사용된 임시파일들을 삭제한다.
}
}
static void startInstall() {
// 프로그램 설치에 필요한 준비를 하는 코드를 적는다.
}
static void copyFiles() {
// 파일들을 복사하는 코드를 적는다.
}
static void deleteTempFiles() {
// 임시파일들을 삭제하는 코드를 적는다.
}
}
위 코드에서 try-catch문을 아래와 같이 try-catch-finally문으로 변경할 수 있습니다.
try {
startInstall(); // 프로그램 설치에 필요한 준비를 한다.
copyFiles(); // 파일들을 복사한다.
} catch (Exception e) {
e.printStackTrace();
} finally {
deleteTempFiles(); // 프로그램 설치에 사용된 임시파일들을 삭제한다.
}
9. 메서드에 예외 선언하기
메서드에 예외를 선언하는 것은 예외를 처리하는 또 다른 방법입니다.
사실 예외를 처리하는 것이 아니라 호출한 메서드로 전달해주는 것이라고 볼 수 있습니다.
예제 1)
class ExceptionEx18 {
public static void main(String[] args) throws Exception {
method1(); // 같은 클래스내의 static 멤버이므로 객체생성없이 직접 호출가능.
}
static void method1() throws Exception {
method2();
}
static void method2() throws Exception {
throw new Exception();
}
}
[실행결과]
Exception in thread "main" java.lang.Exception
at ExceptionEx18.method2(ExceptionEx18.java:11)
at ExceptionEx18.method1(ExceptionEx18.java:7)
at ExceptionEx18.main(ExceptionEx18.java:3)
예외가 발생했을 때, 3개의 메서드 모두(main, method1, method2) 호출 스택에 있었으며, 예외가 발생한 곳은 제일 윗줄에 있는 method2()라는 것과 main메서드가 method1()를, 그리고 method1()은 method2()를 호출했다는 것을 알 수 있습니다.
예제 2)
class ExceptionEx19 {
public static void main(String[] args) throws Exception {
method1(); // 같은 클래스내의 static 멤버이므로 객체생성없이 직접 호출가능.
}
static void method1() throws Exception {
try {
throw new Exception();
} catch (Exception e) {
System.out.println("method1 메서드에서 예외가 처리되었습니다.");
e.printStackTrace();
}
}
}
[실행결과]
method1 메서드에서 예외가 처리되었습니다.
java.lang.Exception
at ExceptionEx19.method1(ExceptionEx98.java:8)
at ExceptionEx19.main(ExceptionEx19.java:3)
예제 3)
예외가 발생한 메서드 내에서 자체적으로 처리
import java.io.*;
class ExceptionEx21 {
public static void main(String[] args) throws Exception {
// command line에서 입력받은 값을 이름으로 갖는 파일을 생성한다.
File f = creatFile(args[0]);
System.out.println(f.getName() + "파일이 성공적으로 생성되었습니다.");
}
static File creatFile(String fileName) {
try {
if (fileName == null || fileName.equals("")) {
throw new Exception("파일이름이 유효하지 않습니다.");
// 예외가 발생한 createFile메서드 자체 내에서 예외를 처리한다.
}
} catch (Exception e) {
// fileName이 부적절한 경우, 파일 이름을 'untitle.txt'로 한다.
fileName = "untitle.txt";
} finally {
File f = new File(fileName); // File클래스의 객체를 만든다.
createNewFile(f); // 생성된 객체를 이용해서 파일을 생성한다.
return f;
}
}
static void createNewFile(File f) {
try {
f.createNewFile(); // 파일을 생성한다.
} catch(Exception e) { }
}
}
만약 java 실행 시 "오류: 기본 클래스... 을(를) 찾거나 로드할 수 없습니다."라는 오류 문구가 뜬다면 환경변수 설정에 들어가
CLASSPATH를 %JAVA_HOME%\lib가 아닌 %JAVA_HOME%\lib;.;로 변경합니다.
예제 4)
메서드 호출 시 넘겨받아야 할 값을 다시 받아야 하는 경우(메서드 내에서 자체적으로 해결이 안 되는 경우)
import java.io.File;
class ExceptionEx22 {
public static void main(String[] args) {
try {
File f = creatFile(args[0]);
System.out.println(f.getName() + "파일이 성공적으로 생성되었습니다.");
} catch (Exception e) {
System.out.println(e.getMessage() + " 다시 입력해 주시기 바랍니다.");
// createFile메서드를 호출한 메서드(main메서드)에서 처리
}
}
static File creatFile(String fileName) throws Exception {
if (fileName == null || fileName.equals("")) {
throw new Exception("파일이름이 유효하지 않습니다."); // 예외 발생
}
File f = new File(fileName); // File클래스의 객체를 만든다.
f.createNewFile(); // 파일을 생성한다.
return f;
}
}
10. 예외 던지기 (re-throwing)
예외 던지기는 예외를 처리한 후에 다시 예외를 생성해서 호출한 메서드로 전달하는 것을 말합니다.
예외가 발생한 메서드와 호출한 메서드, 양쪽에서 예외를 처리해야 하는 경우에 사용합니다.
class ExceptionEx23 {
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; // 다시 예외를 발생시킨다.
}
}
}
[실행결과]
method1메서드에서 예외가 처리되었습니다.
main메서드에서 예외가 처리되었습니다.
11. 사용자 정의 예외 만들기
기존의 예외 클래스를 상속받아서 새로운 예외 클래스를 정의할 수 있습니다.
class MyException extends Exception {
MyException(String msg) {
super(msg); // 조상인 Exception클래스의 생성자를 호출한다.
}
}
에러코드를 저장할 수 있게 ERR_CODE와 getErrCode()를 멤버로 추가
class MyException extends Exception {
// 에러 코드 값을 저장하기 위한 필드를 추가 했다.
private final int ERR_CODE;
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; // 이 메서드는 주로 getMessage()와 함께 사용될 것이다.
}
}
class MyExceptionTest {
public static void main(String[] args) {
try {
throw new MyException("에러 메시지 : 사용자정의 예외가 발생되었습니다.", 200);
} catch (MyException e) {
System.err.println(e.getErrCode());
System.err.println(e.getMessage());
}
}
}
[실행결과]
200
에러 메시지 : 사용자정의 예외가 발생되었습니다.
12. 멀티 캐치 multicatch
try {
// ...
} catch (Exception1 e1) {
e.printStackTrace(); // 중복코드
} catch (Exception2 e2) {
e.printStackTrace(); // 중복코드
} catch (Exception e) {
System.err.println(e.getMessage());
}
예외가 발생할 때, 동일한 예외처리를 하는 경우가 있습니다.
자바 7에서는 다음과 같이 하나의 catch 블록에서 동시에 여러 예외를 묶어서 처리할 수 있도록 했습니다.
try {
// ...
} catch (Exception1 | Exception2 e) {
e.printStackTrace();
} catch (Exception e) {
System.err.println(e.getMessage());
}
13. try-with-resources
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("");
fos = new FileOutputStream("");
} catch (IOException e) {
} finally {
try {
fis.close();
} catch (IOException ie) {
}
try {
fos.close();
} catch (IOException ie) {
}
}
뭔가 자원을 생성하면 해제 코드를 구현을 해줘야 했습니다.
하지만 자바 7에서 try-with-resources라는 특징이 추가되었습니다. try에 자원 객체를 전달하면 finally 블록으로 종료 처리를 하지 않아도 try 코드 블록이 끝나면 자동으로 자원을 종료해주는 기능입니다.
리소스가 AutoCloseable 인터페이스를 구현하고 있다면, 위 코드를 아래와 같이 작성할 수 있습니다.
try (FileInputStream fis = new FileInputStream("");
FileOutputStream fos = new FileOutputStream("")) {
} catch (IOException e) {
}
// in과 out 모두 자동으로 종료됨
'java' 카테고리의 다른 글
[java] StringBuffer getChars (Chars to String) (0) | 2020.10.31 |
---|---|
[java] 임시 비밀번호 생성 (Random vs SecureRandom vs RandomStringUtils) (7) | 2020.10.31 |
[java] 자바 컴파일 : 향상된for이 while로 변경? (0) | 2020.10.11 |
[java] 자바에서 split("|") 사용하기 - 이스케이프 처리 (0) | 2020.10.11 |
[mybatis] insert/update 쿼리 실행 후 결과값 가져오기 (useGeneratedKeys, keyProperty, selectKey) (0) | 2020.10.09 |
댓글