💁♀️ 예외처리(Exception)란,
프로그램 실행 시 발생할 수 있는 예외에 대비하는 것으로 프로그램의 비정상적인 종료를 막고 실행 상태를 유지하는 것
📍 예외처리 방법
1) throws로 위임 : Exception 처리를 호출한 메소드에게 위임
2) try-catch로 처리 : Exception이 발생한 곳에서 직접 처리
public class ExceptionTest {
>>> 예외를 발생시키는 메소드
public void checkEnoughMoney(int price, int money) throws Exception {
System.out.println("가지고 계신 돈은 " + money + "원 입니다.");
if(money >= price) {
System.out.println("상품을 구입하기 위한 금액이 충분합니다.");
} else {
>>> 강제로 예외를 발생시킴
>>> 예외를 발생시킨 뒤 메소드 헤드에 throws 구문을 추가
>>> 예외를 발생시킨 쪽에서는 throws로 예외에 대한 책임을 위임해서
>>> 해당 예외에 대한 처리를 강제화
throw new Exception();
>>> throws로 떠넘겼기 때문에 아래 프로그램 종료 식이 실행되지 않음
}
// 예외가 발생하지 않은 경우에만 실행
System.out.println("즐거운 쇼핑하세요.");
}
public static void main(String[] args) throws Exception {
ExceptionTest et = new ExceptionTest();
// 정상 동작
et.checkEnoughMoney(10000, 50000);
// 예외 발생
et.checkEnoughMoney(50000, 10000);
>>> throws로 떠넘겼기 때문에 아래 프로그램 종료 구문이 실행되지 않음
>>> 메소드 내부에서 예외 발생 시 이후 구문은 동작하지않고 호출한 곳으로 되돌아 옴
>>> 이 메인 메소드 또한 예외를 처리하지않고 위임했기 때문에 프로그램은 비정상적으로
>>> 종료되고 아래 구문은 출력되지 않음
System.out.println("프로그램을 종료합니다.");
}
💻 Mini Console
가지고 계신 돈은 50000원 입니다.
상품을 구입하기 위한 금액이 충분합니다.
즐거운 쇼핑하세요.
가지고 계신 돈은 10000원 입니다.
Exception in thread "main" java.lang.Exception
at com.greedy.section01.exception.ExceptionTest.checkEnoughMoney(ExceptionTest.java:16)
at com.greedy.section01.exception.Application1.main(Application1.java:19)
public static void main(String[] args) {
ExceptionTest et = new ExceptionTest();
try {
>>> 예외 발생 가능성이 있는 메소드는 try 블럭 안에서 호출
et.checkEnoughMoney(10000, 50000); // 정상 동작
et.checkEnoughMoney(50000, 10000); // 예외 발생
>>> 예외가 발생한 위치 하단 코드는 동작 X
System.out.println("============== 상품 구입 가능 =============");
>>> 던져진 예외를 얘가 잡아줌
} catch (Exception e) {
>>> 위의 메소드 호출 시 예외가 발생하는 경우 catch 블럭의 코드를 실행
System.out.println("============== 상품 구입 불가 =============");
}
>>> catch 블럭 동작 후 프로그램 정상 흐름으로 돌아옴
System.out.println("프로그램을 종료합니다.");
}
💻 Mini Console
가지고 계신 돈은 50000원 입니다.
상품을 구입하기 위한 금액이 충분합니다.
즐거운 쇼핑하세요.
가지고 계신 돈은 10000원 입니다.
============== 상품 구입 불가 =============
프로그램을 종료합니다.
💁♀️ 사용자 정의 예외란,
자바 표준 API가 제공하는 예외 클래스만으로 다양한 종류의 예외를 다 표현할 수 없으므로, 직접 예외를 정의하여 사용하는 것
- 예외 클래스의 이름만으로 어떤 예외가 발생했는지 알 수 있도록 사용자 정의 예외를 사용
public class ExceptionTest {
public void checkEnoughMoney(int price, int money) throws PriceNegativeException, MoneyNegativeException, NotEnoughMoneyException {
// : 나를 호출할 때 이 세 가지 Exception이 생길 수 있음
>>> throw Exception - 3개의 예외 클래스는 모두 Exception 클래스의 후손이므로
>>> Exception만으로도 작성할 수 있음 (다형성)
// 상품 가격이 음수인지 확인하고 음수인 경우 예외 발생
if(price < 0) {
throw new PriceNegativeException("상품 가격은 음수일 수 없습니다."); // 생성자를 부르고 불러 throwable의 detailMessage = message에 이 문장이 저장
}
// 가진 돈도 음수인지 확인하고 음수인 경우 예외 발생
if(money < 0) {
throw new MoneyNegativeException("가지고 있는 돈은 음수일 수 없습니다.");
}
// 위의 두 값이 정상 입력 되었더라도 상품 가격이 가진 돈보다 큰 경우 예외 발생
if(money < price) {
throw new NotEnoughMoneyException("가진 돈 보다 상품 가격이 더 비쌉니다.");
}
// 모든 조건을 만족하는 경우 정상적으로 물건 구입 가능
System.out.println("가진 돈이 충분합니다. 즐거운 쇼핑 하세요~");
}
public class PriceNegativeException extends Exception {
public PriceNegativeException(){}
public PriceNegativeException(String message){
super(message);
}
}
public class MoneyNegativeException extends Exception {
public MoneyNegativeException() {}
public MoneyNegativeException(String message) {
super(message);
}
}
public class NotEnoughMoneyException extends Exception {
public NotEnoughMoneyException() {}
public NotEnoughMoneyException(String message) {
super(message);
}
}
public static void main(String[] args) {
ExceptionTest et = new ExceptionTest();
>>> 각각의 예외 상황에 다른 조치가 필요할 경우 예외마다 catch 블럭을 나눠서 작성
// try {
// et.checkEnoughMoney(50000, 30000);
// } catch (PriceNegativeException e) {
// e.printStackTrace();
// } catch (MoneyNegativeException e) {
// e.printStackTrace();
// } catch (NotEnoughMoneyException e) {
// e.printStackTrace();
// }
>>> 모든 예외 상황에 같은 조치를 할 경우 catch 블럭을 multi-catch 블럭으로 작성
try {
// 상품 가격보다 가진 돈이 적은 경우
// et.checkEnoughMoney(50000, 30000);
// 상품 가격을 음수로 입력한 경우
// et.checkEnoughMoney(-50000, 30000);
// 가진 돈을 음수로 입력한 경우
// et.checkEnoughMoney(50000, -30000);
// 정상 동작
et.checkEnoughMoney(30000, 50000);
} catch (PriceNegativeException | MoneyNegativeException | NotEnoughMoneyException e) {
e.printStackTrace();
>>> printStackTrace() : 콘솔 창에 예외를 출력해주는 메소드
}
>>> 예외가 발생하더라도 catch 블럭 실행 후 정상 흐름으로 돌아옴
System.out.println("프로그램을 종료합니다.");
}
💻 Mini Console
상품 가격보다 가진 돈이 적은 경우
com.greedy.section02.userexception.exception.NotEnoughMoneyException: 가진 돈 보다 상품 가격이 더 비쌉니다.
at com.greedy.section02.userexception.ExceptionTest.checkEnoughMoney(ExceptionTest.java:30)
at com.greedy.section02.userexception.Application1.main(Application1.java:38)
프로그램을 종료합니다.
상품 가격을 음수로 입력한 경우
com.greedy.section02.userexception.exception.PriceNegativeException: 상품 가격은 음수일 수 없습니다.
at com.greedy.section02.userexception.ExceptionTest.checkEnoughMoney(ExceptionTest.java:20)
at com.greedy.section02.userexception.Application1.main(Application1.java:41)
프로그램을 종료합니다.
가진 돈을 음수로 입력한 경우
com.greedy.section02.userexception.exception.MoneyNegativeException: 가지고 있는 돈은 음수일 수 없습니다.
at com.greedy.section02.userexception.ExceptionTest.checkEnoughMoney(ExceptionTest.java:25)
at com.greedy.section02.userexception.Application1.main(Application1.java:44)
프로그램을 종료합니다.
정상 동작
가진 돈이 충분합니다. 즐거운 쇼핑 하세요~
프로그램을 종료합니다.
💁♀️ finally란,
예외 처리 구문과 상관 없이 반드시 수행해야 하는 경우 작성하며, 보통 사용한 자원을 반납할 목적으로 사용
>>> finally 블럭에서 사용하려면 레퍼런스 변수를 try 블럭 밖에서 선언해야함
BufferedReader in = null;
>>> FileReader을 사용하려면 Exception을 처리해야함 (따라서 try&catch 생성!)
try {
in = new BufferedReader(new FileReader("test.dat"));
>>> in : test.dat라는 파일을 읽어오게끔하는 객체
String s;
>>> readLine() 메소드도 IOException을 throws 하므로 catch 블럭을 추가해서 예외 처리 구문을 작성해야함
while((s = in.readLine()) != null) { // : in을 통해 한 줄을 읽어오겠음
System.out.println(s); // : 읽은 내용을 출력하겠음
}
} catch (FileNotFoundException | EOFException e) {
>>> FileNotFoundException : 해당 파일을 찾지 못 한 예외
>>> FileNotFoundException과 EOFException을 동시에 처리 (multi-catch)
>>> 같은 레벨의 자손을 한 번에 처리 가능
e.printStackTrace();
} catch (IOException e) { //
>>> IOException : 입출력에 관해 추상화된 예외로 FileNOtFoundException은 IOException의 후손
e.printStackTrace();
} finally {
try {
>>> close() : 입출력에 사용한 스트림을 닫아주는 메소드
>>> IOExcetion을 위임한 메소드이기 때문에 finally 블럭 안이이더라도 예외처리를 중첩으로 해야함
if(in != null) // : null이 아닐 때, close
>>> null일 경우 위에서 in을 통해 생성조차 안되었기 때문에 close할 필요도 없음
in.close(); >>> if(in != null)라는 조건이 없으면 java.lang.NullPointerException 에러
} catch (IOException e) {
e.printStackTrace();
}
}
📌 Ref.
* FileReader라는 클래스의 생성자에 예외를 throws 해놓음. 따라서 사용하는 쪽에서 반드시 예외처리를
해야하기 때문에 try-catch 블럭 안에서 생성자를 호출하여 인스턴스를 생성해야함
* catch 블럭은 여러 개를 작성할 시 상위 타입이 하단에 오고 후손 타입이 먼저 작성되어야함.
상위 타입이 위에 오면 다형성 적용에 의해 아래 catch 블럭까지 도달하지 못 함
(상속관계를 고려하여 위치 설정해야함)
* NullPointerException은 unchecked exception이므로 보통 if 구문으로 해결 가능
💁♀️ try-with-resource란,
close 해야하는 인스턴스의 경우, try 옆에 괄호 안에서 생성하면 해당 try-catch 블럭이 완료될 때 자동으로 close 처리
try (BufferedReader in = new BufferedReader(new FileReader("test.dat"))) {
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
>>> 위와 완전히 동일한 식이지만 훨씬 간결함
public class SuperClass {
// 예외를 던지는 메소드 하나 작성
public void method() throws IOException {}
}
public class SubClass extends SuperClass {
[1] 같은 예외를 던져주는 구문으로 오버라이딩 할 수 있음
@Override
public void method() throws IOException {}
[2] 부모의 예외처리 클래스보다 상위의 예외로는 후손 클래스에서 오버라이딩 할 수 없음
// @Override
// public void method() throws Exception {}
[3] 부모의 예외처리 클래스보다 더 하위에 있는 예외
(즉, 더 구체적인 예외)인 경우에는 오버라이딩 할 수 있음
@Override
public void method() throws FileNotFoundException {}
[4] 예외 없이 오버라이딩 할 수 있음
@Override
public void method() {}