프로그램 오류 종류
- 컴파일 에러(compile-time error): 컴파일 할 때 발생하는 에러
- 런타임 에러(runtime error): 실행 할 때 발생하는 에러
- 논리적 에러(logicla error): 작성 의도와 다르게 동작
Java의 런타임 에러
에러는 어쩔 수 없지만, 예외는 처리하자!
- 에러: 프로그램 코드에 의해서 수습될 수 없는 심각한 오류
- 예외: 프로그램 코드에 의해서 수습될 수 있는 다소 미약한 오류
예외처리의 정의와 목적
- 정의: 프로그램 실행 시 발생할 수 있는 예외의 발생에 대비한 코드를 작성하는 것
- 목적: 프로그램의 비정상 종료를 막고 정상적인 실행상태를 유지하는 것
Exception 과 RuntimeException
- Exception 클래스들: 사용자의 실수와 같은 외적인 요인에 의해 발생하는 예외
- RuntimeException 클래스들: 프로그래머의 실수로 발생하는 예외
try - catch 문
try {
// 예외가 발생할 가능성이 있는 문장들을 넣는다.
} catch (Exception1 e1) {
// Exception1이 발생했을 경우, 이를 처리하기 위한 문장을 적는다.
} catch (Exception2 e2) {
// Exception2이 발생했을 경우, 이를 처리하기 위한 문장을 적는다.
} catch (ExceptionN eN) {
// ExceptionN이 발생했을 경우, 이를 처리하기 위한 문장을 적는다.
}
public class ExceptionTest {
public static void main(String[] args) {
System.out.println(1); // 1
try {
System.out.println(0/0); //2 예외 발생
System.out.println(2); // 건너뜀
} catch (ArithmeticException ae) { // 3
System.out.println(3); // 4
}
System.out.println(4); // 5
}
}
예외가 발생과 catch 블럭
- 예외가 발생하면, 이를 처리할 catch 블럭을 찾아 내려감
- 일치하는 catch 블럭이 없으면, 예외는 처리가 안됨
- Exception이 선언된 catch 블럭은 모든 예외 처리(마지막 catch 블럭)
- Exception이 선언된 catch 블럭을 맨 앞에 놓으면 모든 예외를 처리해버리기 때문에 어떤 예외가 발생했는지 모른다.
public class ExceptionTest2 {
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(5);
}
}
1
2
3
true
ArithmeticException
5
printStackTrace( )와 getMessage( )
printStackTrace( ): 예외 발생 당시의 호출스택(Call Stack)에 있었던 메서드 정보와 예외 메시지를 화면에 출력한다.
getMessage( ): 발생한 예외 클래스의 인스턴스에 저장된 메시지를 얻을 수 있다.
public class ExceptionTest2 {
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) {
ae.printStackTrace();
System.out.println("예외 메세지: " + ae.getMessage());
}
} catch (Exception e) {
System.out.println("Exception");
}
System.out.println(5);
}
}
1
2
3
예외 메세지: / by zero
5
java.lang.ArithmeticException: / by zero
at ex.exception.ExceptionTest2.main(ExceptionTest2.java:10) // main 메서드 10번째 줄
멀티 catch 블럭
- 내용이 같은 catch 블럭을 하나로 합친 것(JDK 1.7 ~)
- 내용이 같을 때만 사용
try {
// 예외가 발생할 가능성이 있는 문장들을 넣는다.
} catch (ExceptionA e1) {
e.printStackTrace();
} catch (ExceptionB e2) {
e.printStackTrace();
}
↓
try {
...
} catch(ExceptionA | ExceptionB e) { // 부모 자식 관계 X: 어차피 조상타입으로 한번에 처리가능 하기 때문에
e.printStackTrace();
}
- ExceptionA 만 가지고 있는 메서드는 호출불가 하다.
- ExceptionA, ExceptionB의 공통 멤버만 사용가능!
- instanceof로 체크한 후 형변환 후 사용 가능!
try {
...
} catch(ExceptionA | ExceptionB e) {
e.methodA(); // Error! ExceptionA에 선언된 methodA() 는 호출불가
if(e instanceof ExceptionA) { // e(예외)가 ExceptionA의 예외인가?
Exception e1 = (ExceptionA)e;
e1.methodA(); // OK. ExceptionA 에 선언된 메서드 호출가능
} else {
// if(e instanceof ExceptionB) {}
}
예외 발생시키기
1. 연산자 new를 이용해서 발생시키려는 예외 클래스의 객체를 만든 다음
Exception e = new Exception("고의로 발생시켰음");
2. 키워드 throw를 이용해서 예외를 발생시킨다.
throw e;
public class ExceptionTest3 {
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 ex.exception.ExceptionTest3.main(ExceptionTest3.java:8)
checked 예외, unchecked 예외
- checked 예외: 컴파일러 예외 처리 여부를 체크(예외 처리 필수)
- Exception과 자손: 체크 예외(try- catch 필수)
- unchecked 예외: 컴파일러가 예외 처리 여부를 체크 안함(예외 처리 선택)
- RuntimeException과 자손들: 언체크 예외(try - catch 선택)
public class ExceptionTest4 {
public static void main(String[] args) {
throw new Exception(); // Exception을 고의로 발생시킨다.(컴파일 오류!!!)
}
}
public class ExceptionTest4 {
public static void main(String[] args) {
throw new RuntimeException(); // RuntimeException을 고의로 발생시킨다.(런타임 에러!!)
}
}
그럼 RuntimeException은 왜 try-catch가 선택적일까?
만약 try-catch가 필수적이라면 배열을 생성할 때 이런 복잡한 코드가 될 것이다.
그럼 거의 모든 코드마다 이런 try-catch를 넣어야 할 것이다.
public class ExceptionTest5 {
public static void main(String[] args) {
try {
int[] arr = new int[10];
System.out.println(arr[0]);
} catch (ArrayIndexOutOfBoundsException ae) {
System.out.println("배열의 인덱스를 벗어났습니다.");
} catch (NullPointerException ne) {
System.out.println("배열의 값이 null 입니다");
}
}
}
메서드에 예외 선언하기
예외를 처리하는 방법: try-catch, 예외 선언(떠넘기기). 은폐(try-catch문 빈칸)
메서드 호출 시 발생가능한 예외를 호출하는 쪽에 알리는 것
void method() throws Exception1, Exception2, ... { //이런 이런 예외들이 일어날 수 있다.
// 메서드 내용
}
// Method()에서 Excetion과 그 자손 예외 발생 가능
void method() throws Exception { // 모든 예외의 종류가 발생할 수 있다.
// 메서드 내용
}
static void startInstall() throws SpaceException, MemoryException { //이런 이런 예외들이 일어날 수 있다.
if(!enoughSpace()) {
throw new SpaceException("설치할 공간이 부족합니다");
}
if(!enoughMemory()) {
throw new MemoryException("메모리가 부족합니다.");
}
}
예제 1.
public class ExceptionTest6 {
public static void main(String[] args) throws Exception {
// 같은 클래스내의 static 멤버이므로 객체 생성없이 직접 호출 가능
method1(); // 1. 호출 // 6. 4. try-catch문이 없으니 JVM에게 전달하고 비정상 종료
}
static void method1() throws Exception {
method2(); // 2. 호출 // 5. try-catch문이 없으니 자신을 호출한 메서드로 올라감
// method1 끝
}
static void method2() throws Exception {
throw new Exception(); // 3. 호출 // 4. try-catch문이 없으니 자신을 호출한 메서드로 올라감
// method2 끝
}
}
Exception in thread "main" java.lang.Exception
at ex.exception.ExceptionTest6.method2(ExceptionTest6.java:16)
at ex.exception.ExceptionTest6.method1(ExceptionTest6.java:11)
at ex.exception.ExceptionTest6.main(ExceptionTest6.java:7)
예제 2.
public class ExceptionTest7 {
public static void main(String[] args) {
try {
File f = createFile(args[0]);
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 클래스의 객체를 만든다.
// File 객체의 createNewFile 메서드를 이용해서 실제 파일을 생성한다.
f.createNewFile();
return f; // 생성된 객체의 참조값 반환
} // createFile() 끝
}
public class ExceptionTest8 {
public static void main(String[] args) {
try {
File f = createFile("");
System.out.println(f.getName() + " 파일이 성공적으로 생성되었습니다"); // "제목없음" 파일 생성
} catch (Exception e) { // 예외발생 X, 밑에서 처리했기 때문에
System.out.println(e.getMessage() + " 다시 입력해주시기 바랍니다.");
}
} // main 종료
static File createFile(String fileName) throws Exception{
try {
if (fileName == null | fileName.equals("")) { // true
throw new Exception("파일 이름이 유효하지 않습니다."); //위 조건을 만족할 때 예외발생
}
} catch (Exception e) { // 위에서 잡는 것이 아니라 여기서 바로 잡아버림, 따라서 위에는 상황도 모름
fileName = "제목없음"; // fileName을 초기화
}
File f = new File(fileName);
f.createNewFile();
return f;
} // createFile() 끝
}
제목없음 파일이 성공적으로 생성되었습니다
예외처리를 작업을 시킨 곳, 작업을 수행한 곳일 지는 우리의 몫!!
finally 블럭
예외 발생 여부와 관계없이 수행되어야 하는 코드를 넣는다.
try {
// 예외가 발생할 가능성이 있는 문장들을 넣는다.
} catch (Exception e) {
// 예외처리를 위한 문장을 넣는다.
} finally {
// 예외의 발생여부에 관계없이 항상 수행되어야 하는 문장을 넣는다.
// finally 블럭은 try-catch문의 맨 마지막에 위치해야한다.
// try 블럭에 return 문이 있어서 try 블럭을 벗어나갈 때도 finally 블럭은 실행된다.
}
예외가 발생하건 아니건 임시파일을 삭제해야 하는 코드가 있다고 가정하자.
deleteTempFiles( )는 무조건 실행되어야 한다.
try {
startInstall();
copyFiles(); // 예외발생!!
deleteTempFiles();
} catch (Exception e) {
e.printStackTrace();
deleteTempFiles();
}
↓
try {
startInstall();
copyFiles();
} catch (Exception e) {
e.printStackTrace();
} finally {
deleteTempFiles();
}
- 코드의 중복이 제거됬다!!
- 예외 발생 여부와 관계없이 수행되어야 하는 코드를 넣는다.
사용자 정의 예외 만들기
우리가 직접 예외 클래스를 정의할 수 있다.
조상은 Exception과 RuntimeException 중에서 선택
public class MyException extends Exception{ // 사용자가 일으키는 예외
MyException(String msg) { // 문자열을 매개변수로 받는 생성자
super(msg); // 조상인 Exception 클래스의 생성자를 호출한다.
}
}
예외 되던지기
예외를 처리한 후에 다시 예외를 발생시키는 것
호출한 메서드와 호출된 메서드 양쪽 모두에서 예외처리하는 것(분담 처리)
양쪽에서 처리(딸이 조금처리, 아빠가 나머지 처리)
public class ExceptionTest9 {
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 메서드에서 예외가 처리되었습니다.
연결된 예외
- 한 예외가 다른 예외를 발생시킬 수 있다.
- 예외 A가 예외 B를 발생시키면, A는 B의 원인 예외(cause exception)
- 어떤 예외를 다른 예외로 감싼다.
Throwable initCause(Throwable cause): 지정한 예외를 원인 예외로 등록
Throwable getCause(): 원인 예외를 반환
RuntimeException(Throwable cause): 필수 -> 선택
void install() throws InstallException {
try {
startInstall(); // SpaceException 발생
copyFiles();
} catch (SpaceException e) {
InstallException ie = new InstallException("설치중 예외발생"); // 예외 생성
ie.initCaues(e); // InstallException의 원인 예외를 SpaceException으로 지정
throw ie; // InstallException을 발생시킨다.
} catch (MemoryException me) {
...
}
}
[이유1] 여러 예외를 하나로 묶어서 다루기 위해
try {
install();
} catch(SpaceException e) {
e.printStackTrace();
} catch(MemoryException e) {
e.printStackTrace();
} catch(....) {
// 무수하게 많은 catch가 있을 수 있다.
} catch(Exception e) {
e.printStackTrace();
}
↓
try {
install();
} catch(InstallException e) {
e.printStackTrace();
} catch(Exception e) {
e.printStackTrace();
}
[이유2] checked 예외를 unchecked 예외로 변경하려 할 때
static void startInstall() throws SpaceException, MemoryException { //예외 필수처리
if(!enoughSpace()) {
throw new SpaceException("설치할 공간이 부족합니다");
}
if(!enoughMemory()) {
throw new MemoryException("메모리가 부족합니다.");
}
}
class SpaceException extend Exception { //예외 필수처리
SpaceException(String msg) {
super(msg);
}
}
SpaceException을 RuntimeException을 하나 만들고 포함시켜버림.
static void startInstall() throws SpaceExceptionn {
if(!enoughSpace()) {
throw new SpaceException("설치할 공간이 부족합니다");
}
if(!enoughMemory()) {
throw new RuntimeException(new MemoryException("메모리가 부족합니다")); // 필수 -> 선택
}
}
'복습' 카테고리의 다른 글
[Java 복습] Object 클래스 (0) | 2024.05.03 |
---|---|
[Java 복습] hashCode( ), toString( ) (0) | 2024.05.03 |
[Java 복습] 내부 클래스 (0) | 2024.05.02 |
[Java 복습] 인터페이스의 장점 (0) | 2024.05.02 |
[Java 복습] 다형성의 장점 (0) | 2024.04.30 |