컴파일 에러(compile-time error) : 컴파일할 때 발생하는 에러
논리적 에러(logical error) : 작성 의도와 다르게 동작하는 에러
런타임 에러(runtime error) : 실행할 때 발생하는 에러
예외처리(exception handling)의 정의와 목적
상속계층도
Exception 클래스와 그 자손들 : 사용자의 실수와 같은 외적인 요인에 의해 발생하는 예외
- IOException : 입출력 예외
- ClassNotFoundException : 클래스 파일(*.class)이 존재하지 않는 예외
RumtimeException 클래스와 그 자손들 : 프로그래머의 실수로 발생하는 예외
- ArithmeticException : 산술 계산 예외(0으로 나눌 때)
- ClassCastException : 형변환 예외
- NullPointerException : 널포인터 예외(가리키는 객체가 null 일 때)
- IndexOutOfBoundsException : 배열 범위를 벗어날 때 발생하는 예외
class Ex8_1 {
public static void main(String[] args) {
System.out.println(1);
try {
System.out.println(2);
System.out.println(3);
} catch (Exception e) {
System.out.println(4);
}
System.out.println(5);
}
}
결과
1
2
3
5
class Ex8_2 {
public static void main(String[] args) {
System.out.println(1);
try {
System.out.println(0/0);
System.out.println(2);
} catch (ArithmeticException ae) {
System.out.println(3);
}
System.out.println(4);
}
}
결과
1
3
4
System.out.printf("2 ");
: try 블럭 내에서 예외가 발생하면 그 다음 문장들은 실행되지 않는다.class Ex8_4 {
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
catch (Exception e){ System.out.println("Exception"); }
: Exception 은 모든 예외의 최고 조상이기 때문에 모든 예외 처리가 가능하다. 그래서 마지막 catch 블럭에 쓰인다.printStackTrace() : 예외 발생 당시의 호출스택(Call stack)에 있었던 메서드의 정보와 예외 메시지를 화면에 출력한다.
getMessage() : 발생한 예외 클래스의 인스턴스에 저장된 메시지를 얻을 수 있다.
class Ex8_5 {
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 Ex8_5.main(Ex8_5.java:8)
예외 메시지 = / by zero
6
ae.printStackTrace();
: java.lang.ArithmeticException: / by zeroSystem.out.println("예외 메시지 : " + ae.getMessage());
:내용이 같은 catch 블럭을 하나로 합친 것
try { try {
... ...
} catch (ExceptionA e) { } catch (ExceptionA | ExceptionB e) {
e.printStackTrace(); -> e.printStackTrace();
} catch (ExceptionB e2) { }
e2.printStackTrace();
}
부모, 자식 관계의 catch 블럭은 멀티 catch 블럭으로 사용 불가능
참조변수로 사용할 수 있는건 두 예외객체의 공통멤버만 사용 가능
try {
...
} catch (ExceptionA | ExceptionB e) {
e.methodA(); // 에러
if(e instanceof ExceptionA) {
ExceptionA e1 = (ExceptionA) e; // 형변환
e1.methodA();
} else {
...
}
}
class Ex8_6 {
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("프로그램이 정상 종료되었습니다.");
}
}
throw new Exception("고의로 발생");
위의 두 문장을 한 줄로 줄일 수 있다.checked 예외(Exception과 자손) : 컴파일러가 예외 처리 여부를 체크(예외 처리(try-catch) 필수)
unchecked 예외(RuntimeException과 자손) : 컴파일러가 예외 처리 여부를 체크 안함(예외 처리 선택)
class Ex8_7 {
public static void main(String[] args) {
throw new RuntimeException();
}
}
결과
Exception in thread "main" java.lang.RuntimeException
at Ex8_7.main(Ex8_7.java:3)
예외를 처리하는 방법 : try-catch 문, 예외 선언하기
메서드가 호출 시 발생 가능한 예외를 호출하는 쪽에 알리는 것
static void startInstall() throws SpaceException, MemoryException {
if(!enoughSpace())
throw new SpaceException("설치할 공간이 부족합니다.");
if(!enoughMemory())
throw new MemoryException("메모리가 부족합니다.");
}
throws
, 예외를 발생시키는 키워드는 throw
잘 구별하자.예외 선언할때는 예외 처리가 필수인 checked 예외만 선언해준다. unchecked 예외는 안적는다.
예외를 선언만 해서는 안된다. 메서드를 호출하는 곳이든, 메서드를 수행하는 곳이든지 예외를 처리해줘야 한다.
import java.io.*;
class Ex8_10 {
public static void main(String[] args) {
try {
File f = createFile("");
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;
}
}
결과
파일 이름이 유효하지 않습니다. 다시 입력해 주시기 바랍니다.
import java.io.*;
class Ex8_10 {
public static void main(String[] args) {
File f = createFile("");
System.out.println( f.getName()+" 파일이 성공적으로 생성되었습니다.");
}
static File createFile(String fileName) {
try {
if (fileName == null || fileName.equals(""))
throw new Exception("파일 이름이 유효하지 않습니다.");
} catch (Exception e) {
fileName = "제목없음.txt";
}
File f = new File(fileName);
try {
f.createNewFile();
} catch (IOException e) {
throw new RuntimeException(e);
}
return f;
}
}
결과
제목없음.txt 파일이 성공적으로 생성되었습니다.
예외 처리를 예외가 발생한 곳에서 해줘야 하는지, 아니면 메서드를 호출한 곳에서 해줘야 하는지 적절히 판단해서 예외를 처리해줘야 한다.
예외 발생 여부와 상관없이 수행되어야 하는 코드를 넣는다.
try-catch 문의 맨 마지막에 위치해야한다.
try 블럭 안에 return 문이 있어서 try 블럭을 벗어나갈 때도 finally 블럭은 실행된다.
try { try {
startInstall(); startInstall();
copyFiles(); copyFiles();
deleteTempFiles(); } catch (Exception e) {
} catch (Exception e) { -> e.printStackTrace();
e.printStackTrace(); } finally {
deleteTempFiles(); deleteTempFiles(); // 코드의 중복 제거
} }
직접 예외 클래스를 정의할 수 있다.
사용자 정의 예외 만드는 법
class MyException extends Exception {
MyException(String msg) { // 문자열을 매개변수로 받는 생성자
super(msg); // 조상인 Exception 클래스의 생성자를 호출
}
}
예외를 처리한 후 다시 예외를 발생시키는 것
호출한 메서드와 호출된 메서드 양쪽 모두에서 예외 처리하는 것
class Ex8_12 {
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)
class Ex8_13 {
public static void main(String[] args) {
try {
install();
} catch(InstallException e) {
e.printStackTrace();
} catch(Exception e) {
e.printStackTrace();
}
}
static void install() throws InstallException {
try {
startInstall();
copyFiles();
} catch (SpaceException e) {
InstallException ie = new InstallException("설치 중 예외 발생");
ie.initCause(e);
throw ie;
} catch (MemoryException me) {
InstallException ie = new InstallException("설치 중 예외 발생");
ie.initCause(me);
throw ie;
} finally {
deleteTempFiles();
}
}
static void startInstall() throws SpaceException, MemoryException {
if(!enoughSpace()) {
throw new SpaceException("설치할 공간이 부족합니다.");
}
if (!enoughMemory()) {
throw new MemoryException("메모리가 부족합니다.");
// throw new RuntimeException(new MemoryException("메모리가 부족합니다."));
}
}
static void copyFiles() { /* */ }
static void deleteTempFiles() { /* */ }
static boolean enoughSpace() {
return false;
}
static boolean enoughMemory() {
return true;
}
}
class InstallException extends Exception {
InstallException(String msg) {
super(msg);
}
}
class SpaceException extends Exception {
SpaceException(String msg) {
super(msg);
}
}
class MemoryException extends Exception {
MemoryException(String msg) {
super(msg);
}
}
catch (SpaceException e) {
InstallException ie = new InstallException("설치 중 예외 발생");
ie.initCause(e); // SpaceException 을 원인 예외로 지정
throw ie;
}
SpaceException 예외는 InstallException 예외의 원인 예외
SpaceException 예외와 InstallException 예외는 연결된 예외
SpaceException 예외를 InstallException 예외 안에 포함시킴
1. 여러 예외를 하나로 묶어서 다루기 위해서
static void install() throws InstallException {
try {
startInstall();
copyFiles();
} catch (SpaceException e) {
InstallException ie = new InstallException("설치 중 예외 발생");
ie.initCause(e);
throw ie;
} catch (MemoryException me) {
InstallException ie = new InstallException("설치 중 예외 발생");
ie.initCause(me);
throw ie;
} finally {
deleteTempFiles();
}
}
결과
InstallException: 설치 중 예외 발생
at Ex8_13.install(Ex8_13.java:17)
at Ex8_13.main(Ex8_13.java:4)
Caused by: SpaceException: 설치할 공간이 부족합니다.
at Ex8_13.startInstall(Ex8_13.java:31)
at Ex8_13.install(Ex8_13.java:14)
... 1 more
InstallException: 설치 중 예외 발생 -> 발생 예외(대략 정보)
Caused by: SpaceException: 설치할 공간이 부족합니다. -> 원인 예외(세부 정보)
예외가 발생된 대략적인 정보와 세부적인 정보를 볼 수 있어서 보는 사람 입장에서 보기 편하다.
예외를 처리하는 입장에서도 여러 예외를 묶어서 다루는게 더 간단해진다.
2. checked 예외(예외 필수)를 unchecked 예외(예외 선택)로 변경하려고 할 때
static void startInstall() throws SpaceException, MemoryException {
if(!enoughSpace()) {
throw new SpaceException("설치할 공간이 부족합니다.");
}
if (!enoughMemory()) {
throw new MemoryException("메모리가 부족합니다.");
}
}
static void startInstall() throws SpaceException {
if(!enoughSpace()) {
throw new SpaceException("설치할 공간이 부족합니다.");
}
if (!enoughMemory()) {
throw new RuntimeException(new MemoryException("메모리가 부족합니다."));
}
}
RuntimeException 을 만들고 MemoryException 을 원인 예외로 등록
unchecked 예외로 바뀌었기 때문에 선언부에서 MemoryException 삭제
처음 예외를 만들 때와 현재 환경이 많이 달라졌기 때문에 checked 예외 중에서도 불필요하게 try-catch 문을 쓸 필요가 없는 예외가 생기게 됨. 이럴 때 연결된 예외를 사용