어제 자바에서 규정하는 오류와 예외에 대해서 알아보았다.
오늘은 실제로 예외를 처리하는 방법에 대해서 알아보자.
예외처리에는 꼭 예외처리를 해야 프로그램이 실행되는 Checked 예외
와 UnChecked 예외
가 있다.
예외 처리 방법에는 잘 알려진 try-catch
문과 throw
문(예외 던지기), chained Exception
문(예외 연결)이 있다.
try문에서 특정 예외 클래스가 발생하면 그 클래스에 대한 catch문이 실행되고 프로그램이 비정상 종료되지 않는다.
예외가 발생한다고 무조건 catch문이 실행되는 것은 아니고, 발생한 예외에 대한 catch문이 실행된다.
✍️ 실제코드
public class ExcetionTest {
public static void main(String[] args) {
int[] arr = new int[5];
try {
for(int i = 0; i <= 5; i++) {
arr[i] = i + 1; // → ArrayIndexO... 예외발생
System.out.println(arr[i]);
}
} catch (ArrayIndexOutOfBoundsException e) {//try문에 대한 예외
System.out.println(e);
System.out.println("예외처리를 마쳤습니다.");
}
System.out.println("프로그램 정상종료");
}
}
👉 실행화면
1
2
3
4
5
java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 5
예외처리를 마쳤습니다.
프로그램 정상종료
모든 예외에 대한 catch문을 작성하는건 불가능하다. 따라서 예외클래스의 상위클래스인 Excetion
에 대한 catch문을 작성하면 나머지의 모든 예외 클래스에 대해 작성할 수 있다.
다만, Excetion에 대한 문장은 맨 뒤에 와야한다. (상위클래스로 예외클래스를 한번에 처리하기 때문이다.)
finally문은 예외발생여부에 상관없이 무조건 실행된다.
try문이나 catch문에 return문이 있더라도 실행된다.
✍️실행코드
public class ExcetionTest {
public static void main(String[] args) {
try {
System.out.println("정상처리");
return;
} catch (Exception e) {
System.out.println("예외처리");
return;
} finally {
System.out.println("마지막문장");
}
//System.out.println("프로그램 정상종료");
}
}
👉실행 화면
정상처리
마지막문장
여러 예외에 대한 catch문을 | 기호를 사용해서 한번에 작성할 수 있다.
try {
// ...
} catch (NullPointException | ArrayIndexOutOfBoundsExcetion e) {
// ...
}
try-with-resources문에서 말하는 "resources"는 파일, 데이터베이스, 네트워크와 같은 외부의 데이터를 말한다.
resources는 사용 후에 닫아야하는 특징을 가진다.
👉 리소스에 대한 try-catch문을 작성해야하는데, 예외 발생여부와 상관없이 리소스 해제문 작성이 필수이고, 이는 또 try-catch문의 작성이 요구되어 코드의 가독성이 안좋아진다는 단점이 있다.
자바에서는 이를 위해 파일을 열거나 자원을 할당하는 리소스 선언부분을 try문의 괄호안에 선언하면 try 블록이 끝날 때 자동으로 리소스를 해제해준다.
✍️ 예제코드
import java.io.FileWriter;
import java.io.IOException;
public class Main {
public static void main(String[] args) {
try (FileWriter file = new FileWriter("data.txt")) { // 파일을 열고 모두 사용되면 자동으로 닫아준다
file.write("Hello World");
} catch (IOException e) {
e.printStackTrace();
}
}
}
➕ try에 괄호안에는 세미콜론으로 구분해 여러 리소스를 선언할 수 있다.
리소스를 자동 해제해주는 try-with-resources 문으로 사용되기 위해서는 해당 객체가 AutoCloseable 인터페이스를 구현한 클래스여야한다.
예외 클래스가 가지고 있는 메서드를 이용해 예외 메세지를 출력할 수 있다.
printStackTrace()
getMessage()
예외를 발생시켜 로그에 기록하고 싶을 때 throw
키워드를 사용하여 예외를 발생시킬 수 있다.
throw
예약어와 new
생성자로 로 원하는 예외 클래스를 초기화하여 발생시킬 수 있다.
이때 생성자의 매개변수는 getMessage() 메서드에서 출력할 메세지가 된다.
try{
Scanner scanner = new Scanner(System.in);
System.out.println("자연수 입력 : ");
int input = scanner.nextInt();
if(input <= 0){
throw new ArithmeticException("사용자가 자연수가 아닌 수를 입력함.");
}
} catch(ArithmeticException e){
System.out.println(e.getMessage()); // 사용자가 자연수가 아닌 수를 입력함을 출력
}
}
throws
는 해당 메서드 내애서 직접 예외처리를 하지 않고 예외를 호출한 곳으로 떠넘긴다.
여러 코드에서 호출돼 사용될 때, 호출 상황에 맞게 로그를 남기거나 호출 부분에서 예외처리를 하도록 한다.
✍️ 실행코드
loadClass메서드는 예외처리가 필수인 코드를 가지고 있다.
FileInputStream fis = new FileInputStream(fileName);
→ FileNotFoundException에 대한 예외처리 필수
Class c = Class.forName(className);
→ ClassNotFoundException에 대한 예외처리 필수
그런데 예외가 발생할 수 있는 메서드 내에서 예외처리를 하는 것이 아니라, 이 메서드를 호출하는 곳에서 예외처리를 하도록 throws
예약어를 사용하는 것이다.
package Do_It_Java_14;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public class ThrowsExcetion {
public Class loadClass(String fileName, String className) throws FileNotFoundException, ClassNotFoundException {
FileInputStream fis = new FileInputStream(fileName);
Class c = Class.forName(className);
return c;
}
아래 호출 부분에서 각각의 예외에 대한 catch블럭을 작성한 것을 확인 할 수 있다.
public static void main(String[] args) {
ThrowsExcetion test = new ThrowsExcetion();
try {
test.loadClass("a.text", "java.lang.String");
} catch (FileNotFoundException e) {
System.out.println(e.getMessage());
} catch (ClassNotFoundException e) {
System.out.println(e.getMessage());
}
}
}
👉 실행화면
a.text (지정된 파일을 찾을 수 없습니다)
만약 main메서드에서까지 떠넘기면 JVM으로 떠넘겨져 프로그램이 종료된다.
✍️ 예시코드
import java.io.FileInputStream;
import java.io.FileNotFoundException;
public class ThrowsExcetion {
public Class loadClass(String fileName, String className) throws FileNotFoundException, ClassNotFoundException {
FileInputStream fis = new FileInputStream(fileName);
Class c = Class.forName(className);
return c;
}
public static void main(String[] args) throws FileNotFoundException, ClassNotFoundException {
ThrowsExcetion test = new ThrowsExcetion();
test.loadClass("a.text", "java.lang.String");
}
}
👉 실행화면
프로그램이 강제 종료됌
Exception in thread "main" java.io.FileNotFoundException: a.text (지정된 파일을 찾을 수 없습니다)
at java.base/java.io.FileInputStream.open0(Native Method)
at java.base/java.io.FileInputStream.open(FileInputStream.java:216)
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:157)
at java.base/java.io.FileInputStream.<init>(FileInputStream.java:111)
at Do_It_Java_14.ThrowsExcetion.loadClass(ThrowsExcetion.java:8)
at Do_It_Java_14.ThrowsExcetion.main(ThrowsExcetion.java:16)
try-catch문을 사용하느냐, throws를 하느냐에 따라서 자바 코드의 작업 단위가 달라질 수 있기 때문에 자신의 프로젝트에 따라 적절한 예외 처리 로직을 짜야한다.
더 알아보기
예외 클래스도 상속이 있기 때문에 부모예외로 감싸 예외의 다향성처럼 다룰 수 있다.
예외 A가 발생했을 때 예외 B로 감싸서 throw를 할 수 있다.
이때 예외 A를 "원인 예외"라고 한다.
예외B initCause (예외A)
: 지정한 예외를 원인 예외로 등록예외B getCause()
: 원인예외를 반환✍️ 예시코드
1. startInstall() 메서드에서 SpaceException 이라는 예외가 발생
2. catch 문에서 InstallExceptoin 예외 클래스를 새로 생성
3. 그리고 InstallException 객체의 메서드 initCause()를 이용해 SpaceException 타입의 객체를 넣어 실행
4. 그러면 SpaceException 예외는 InstallException 예외에 포함되게 된다. (원인 예외)
5. InstallException 예외 객체발생시키고 밖으로 throws 한다.
6. 메인메서드에서 InstallException 예외를 catch하고 getCause() 메서드를 통해 원인 예외 로그를 출력한다
class InstallException extends Exception { ... }
class SpaceException extends Exception { ... }
class MemoryException extends Exception { ... }
public class Main {
public static void main(String[] args) {
try {
install();
} catch (InstallException e) {
System.out.println("원인 예외 : " + e.getCause()); // 원인 예외 출력
e.printStackTrace();
}
}
public static void install() throws InstallException {
try {
throw new SpaceException("설치할 공간이 부족합니다."); // SpaceException 발생
} catch (SpaceException e) {
InstallException ie = new InstallException("설치중 예외발생"); // 예외 생성
ie.initCause(e); // InstallException의 원인 예외를 SpaceException으로 지정
throw ie; // InstallException을 발생시켜 상위 메서드로 throws 된다.
} catch (MemoryException e) {
// ...
}
}
}
throw new RuntimeException(new IOException("설치할 공간이 부족합니다."));
// Checked 예외인 IOException을 Unchecked 예외인
RuntimeException으로 감싸 Unchecked 예외로 변신 시킨다
자바에서 제공하는 예외 말고도 원하는 예외가 따로 있을 수 있다.
그럴 때 사용자 예외를 만들 수 있다.
사용자 정의 예외 클래스를 구현할 때는 기존 JDK에서 제공하는 예외클래스중 가장 유사한 클래스를 상속받는 것이 좋다. (유사한 예외 클래스를 모르는 상황에서는 가장 상위클래스인 EXcetion클래스를 상속받는 것이 좋다.)
사용자 예외 클래스는 사용자가 원하는 발생시점에 throw
를 통해서 발생시켜야한다.
✍️실행코드
package Do_It_Java_14;
import java.util.Scanner;
class IDExcetion extends Exception {
private String msg;
public IDExcetion(String msg) {
super(msg);
this.msg= msg;
}
public void printIDExcetionMessage() {
System.out.println(this.msg);
}
}
class IDformat {
private String ID;
public void setID(String ID) throws IDExcetion {
if(ID == null) {
throw new IDExcetion("아이디가 null");
} else if (ID.length() < 8 || ID.length() > 20 ) {
throw new IDExcetion("아이디는 8자 이상, 20자 이하입니다.");
} else {
this.ID = ID;
}
}
}
public class CustomExcetionTest {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
IDformat iDformat = new IDformat();
try {
String ID = sc.nextLine();
iDformat.setID(ID);
} catch (IDExcetion e) {
e.printIDExcetionMessage();
}
}
}
휴~ 재미읎다~