💡 오류(Error)와 예외(Exception) 오류 : 시스템 상에서 프로그램에 심각한 문제가 발생하여 실행중인 프로그램이 종료되는 것. 개발자가 미리 예측하거나 코드로 처리하는 것이 불가능 (ex. JVM 에러, 정전, 컴퓨터 자체 하드웨어적인 문제 등)
예외 : 개발자가 미리 예측하고 처리할 수 있는 미약한 오류. 오류와 마찬가지로 실행중인 프로그램을 종료시키는 것이 일반적
예외 상황의 경우는 개발자가 적절히 처리하여 코드의 흐름을 컨트롤(비정상적인 종료를 할 수도 있고 그렇지 않게 할 수도 있게) 할 수 있다.
1-1-2. 예외 처리를 사용하는 이유
💡 오류나 예외는 사용자에게 좋지 않은 사용 경험을 제공. 하지만 미리 예측하고 컨트롤 할 수 있는 예외를 처리함으로써 프로그램이 예상치 못한 상황에 봉착하지 않도록 코드의 안정성과 신뢰성을 높여 이를 미연에 방지하거나 의도한 방향으로 컨트롤 할 수 있다. 또한 개발 시에도 디버깅을 용이하게 해서 예외가 발생한 원인과 위치도 쉽게 파악할 수 있어 용이
2. 예외 클래스의 종류
2-1. 예외 클래스의 계층구조
오류와 예외는 모두 Throwable을 상속 받는다.
예외의 최상위 클래스는 Exception 클래스.
Unchecked Exception 계열은 기본적 이미 처리되어 있고 실행 중인 프로그램이 종료되게 작성.
Checked Exception 계열은 반드시 예외 처리를 해야 하고 하지 않으면 컴파일 에러가 발생.
RuntimeException 타입의 예외들은 런타임 시점에 해당 예외 클래스 타입의 Exception이 발생.
2-2. RuntimeException 후손 클래스 몇 가지
ArithmeticException 0으로 나누는 경우 발생
ArrayIndexOutOfBoundsException 배열의 index 범위를 넘어서 참조하는 경우 발생
NullPointerException 인스턴스가 참조되지 않은 상태(Null)로 인스턴스에 접근하는 경우 발생
ClassCastException 형변환(Cast 연산자 사용) 시 자료형에 문제가 있을 때 발생
NegativeArraySizeException 배열 크기를 음수로 지정한 경우 발생
3. 예외 처리 방법
package com.ohgiraffers.section01.exception;
public class Application {
/* 설명. try-catch 대신 throws로 처리하면 메소드를 호출한 대상에게 예외처리를 위임한다. */
// public static void main(String[] args) throws Exception {
public static void main(String[] args) {
/* 수업목표. 예외에 대해 이해하고 이를 처리하기 위한 문법을 활용할 수 있다. */
/* 필기.
* 1. throws를 통한 위임
* 2. try-catch를 통한 처리
* */
ExceptionTest et = new ExceptionTest();
/* 설명. try-catch를 통해 예외상황을 원하는 의도대로 처리할 수 있다. */
try {
/* 설명. 우리가 처리할 예외가 발생할 수 있는 적당한 범위를 try 블럭으로 감싼다. */
et.checkEnoughMoney(10000, 50000);
et.checkEnoughMoney(50000, 10000); // 오류 발생!!!!!
System.out.println("이전에 문제가 없었으면 실행될 추력 구문"); // 문제가 있으면 출력 되지 않음.
} catch (Exception e) {
/* 설명. 우리가 원하는 방식대로 try 블럭에서 발생한 예외 타입 객체를 활용해서 처리할 수 있다. */
/* 설명. try 블럭에서 예외가 발생하지 않으면 실행되지 않는 블럭 */
System.out.println("뭔가 예외가 발생했나 보네?");
System.out.println("그 예외는 " + e.getMessage() + "!!!!");
System.out.println("돈 좀 넉넉히 들고 다니지!~");
/* 설명. 내가 원하는 시점에 프로그램을 종료할 수 있음 */
// System.exit(0); // 프로그램 종료
/* 설명. JVM 처럼 우리도 예외가 각각 어떤 흐름으로 발생하는지 처리할 수 있음 */
e.printStackTrace(); // 프로그램 종료하는 기능은 없음
}
// 메소드 안에서 처리를 해야하기 때문에 메인에서 처리하기 힘들다....
// 메인에서 처리하기 위해 try-catch 사용!
// et.checkEnoughMoney(50000,10000); // JVM이 처리 / throws 할 때 잠깐 활용한 구문
// // main에서 throws Exception 사용 시
// try {
// int num = 10;
// System.out.println(num / 0);
// } catch (ArithmeticException e) {
//
// /* 설명. unchecked exception도 우리가 처리할 수 있고 exception 객체까지 활용할 수 있다. */
// System.out.println("잘 좀 나누지~ 0이 뭐냐~");
// System.out.println(e.getMessage() + "라잖아~");
// }
System.out.println("프로그램을 종료합니다.");
}
}
package com.ohgiraffers.section01.exception;
public class ExceptionTest {
public void checkEnoughMoney(int price, int money) throws Exception {
System.out.println("가지고 계신 돈은 " + money + "원 입니다.");
if (money >= price) {
System.out.println(price + "원 상품을 구입하기 위한 금액이 충분합니다.");
return;
}
// System.out.println("호주머니 상황이 딱하군요.");
throw new Exception("호주머니 상황이 딱하군요."); // 여기에 매개변수는? -> 활용 가능(e.getMessage())
// 다형성 적용 가능 (ArithmeticException 이라 해도 가능)
}
}
3-1. throws로 위임
💡 Exception이 발생하는 메소드(또는 생성자)를 호출한 상위 메소드에게 처리를 위임하는 방식.
3-2. try-catch(또는 try-catch-finally)로 처리
💡 발생한 Exception을 직접 처리하는 방식.
try 블럭
예외(Exception)가 발생할 가능성이 있는 코드를 포함하여 작성하는 블럭
catch 블럭
try 블럭에서 예외 발생 시 해당 예외 타입(Exception 클래스 타입)에 대한 처리를 기술하는 블럭.
여러 개의 catch 블럭을 이어서 사용할 수 있다.
상위 타입의 예외를 처리하는 catch 블럭이 아래 쪽에 위치해야 함.
finally 블럭
예외 발생 여부와 상관 없이 꼭 실행되어 처리해야 할 코드가 있으면 작성하는 블럭.
주로 java.io나 java.sql 패키지의 메소드 처리 시 자원 반납을 위해 사용.
3-3. 오버라이딩 시 예외 발생 가능 범위
상속 시 오버라이딩하는 메소드는 부모 클래스의 원본 메소드보다 더 상위 타입의 예외를 발생 시키면 안된다.
package com.ohgiraffers.section04.override;
import java.io.IOException;
public class Application {
public static void main(String[] args) {
// SuperClass sc = new SuperClass();
/* 설명. 다형성 적용 시 */
SuperClass sc = new SubClass();
try {
/* 설명. 동적 바인딩에 의해 자식의 오버라이딩한 메소드가 실행 된다면(동적 바인딩) */
sc.method();
} catch (IOException e) { // 부모의 method()만 고려해 처리한 예외처리가 문제 될 수 있다.
throw new RuntimeException(e);
}
}
}
package com.ohgiraffers.section04.override;
import java.io.IOException;
public class SuperClass {
public void method() throws IOException {
}
}
package com.ohgiraffers.section04.override;
import java.io.FileNotFoundException;
import java.io.IOException;
public class SubClass extends SuperClass {
/* 설명. 부모 메소드의 예외와 같은 범위 처리(pass) */
@Override
public void method() throws IOException {
super.method();
}
/* 설명. 부모 메소드와 달리 예외를 발생시키지 않는다면(pass) */
@Override
public void method() {
}
/* 설명. 부모 메소드보다 더 낮은(적은) 범위의 예외 발생(pass) */
@Override
public void method() throws FileNotFoundException {
}
/* 설명. 부모의 메소드보다 상위 타입의 예외 발생(fail) - 컴파일 에러 발생 */
@Override
public void method() throws Exception {
}
}
userexception
package com.ohgiraffers.section02.userexception;
import com.ohgiraffers.section02.userexception.exception.MoneyNegativeException;
import com.ohgiraffers.section02.userexception.exception.NotEnoughMoneyException;
import com.ohgiraffers.section02.userexception.exception.PriceNegativeException;
public class Application1 {
public static void main(String[] args) {
/* 수업목표. 사용자 정의형 예외클래스 정의 후 발생한 사용자 예외들을 처리할 수 있다. */
ExceptionTest et = new ExceptionTest();
try {
et.checkEnoughMoney(30000,40000);
// et.checkEnoughMoney(-30000,40000);
// et.checkEnoughMoney(30000,-40000);
et.checkEnoughMoney(100000,10);
} catch (PriceNegativeException e) {
System.out.println(e.getMessage());
} catch (MoneyNegativeException e) {
System.out.println(e.getMessage());
} catch (NotEnoughMoneyException e) {
System.out.println(e.getMessage());
} catch (Exception e) {
System.out.println("어떤 예외든 커몬");
System.out.println("어떤 예외든 메시지는: " + e.getMessage()); // 부모 exception일수록 아래에 작성해야 한다.
}
}
}
package com.ohgiraffers.section02.userexception;
public class Application2 {
public static void main(String[] args) {
/* 수업목표. finally를 활용한 try-catch문 이해하고 활용하기 */
ExceptionTest et = new ExceptionTest();
try {
et.checkEnoughMoney(20000,50000);
} catch (Exception e) {
System.out.println("유효성 검사 시 문제 발생: " + e.getMessage());
} finally {
/* 설명. try 구문에서 예외가 발생하든 하지 않든 반드시 실행해야 할 구문이 있담녀 작성하는 부분 */
System.out.println("finally 블럭의 내용 동작함...");
}
System.out.println("프로그램을 종료합니다.");
}
}
package com.ohgiraffers.section02.userexception;
import com.ohgiraffers.section02.userexception.exception.MoneyNegativeException;
import com.ohgiraffers.section02.userexception.exception.NotEnoughMoneyException;
import com.ohgiraffers.section02.userexception.exception.PriceNegativeException;
public class Application3 {
public static void main(String[] args) throws NotEnoughMoneyException, MoneyNegativeException, PriceNegativeException {
/* 수업목표. multi-catch 구문을 이해하고 활용할 수 있다. */
ExceptionTest2 et = new ExceptionTest2();
try {
et.checkEnoughMoney(20000,10);
} catch (PriceNegativeException | MoneyNegativeException e) {
System.out.println("두 개의 예외 처리 방식");
} catch (NotEnoughMoneyException e) {
System.out.println("한 개의 예외 처리 방식");
e.printStackTrace();
} finally {
System.out.println("반드시 실행되어야 하는 구문");
}
System.out.println("프로그램 종료");
}
}
package com.ohgiraffers.section02.userexception;
import com.ohgiraffers.section02.userexception.exception.MoneyNegativeException;
import com.ohgiraffers.section02.userexception.exception.NotEnoughMoneyException;
import com.ohgiraffers.section02.userexception.exception.PriceNegativeException;
public class ExceptionTest {
// public void checkEnoughMoney(int price, int money) throws PriceNegativeException, MoneyNegativeException, NotEnoughMoneyException {
public void checkEnoughMoney(int price, int money) throws Exception { // 다형성 때문에 가능
if (price < 0) {
throw new PriceNegativeException("상품 가격은 음수일 수 없습니다.");
}
if (money < 0) {
throw new MoneyNegativeException("가지고 있는 돈은 음수일 수 없습니다.");
}
if (money < price) {
throw new NotEnoughMoneyException("가진 돈보다 상품 가격이 더 비쌉니다.");
}
System.out.println("가진 돈이 충분하시군요 고객님! 즐거운 쇼핑 되세요!");
}
}
package com.ohgiraffers.section02.userexception;
import com.ohgiraffers.section02.userexception.exception.MoneyNegativeException;
import com.ohgiraffers.section02.userexception.exception.NotEnoughMoneyException;
import com.ohgiraffers.section02.userexception.exception.PriceNegativeException;
public class ExceptionTest2 {
public void checkEnoughMoney(int price, int money) throws PriceNegativeException, MoneyNegativeException, NotEnoughMoneyException {
if (price < 0) {
throw new PriceNegativeException("상품 가격은 음수일 수 없습니다.");
}
if (money < 0) {
throw new MoneyNegativeException("가지고 있는 돈은 음수일 수 없습니다.");
}
if (money < price) {
throw new NotEnoughMoneyException("가진 돈보다 상품 가격이 더 비쌉니다.");
}
System.out.println("가진 돈이 충분하시군요 고객님! 즐거운 쇼핑 되세요!");
}
}
userexception.exception
package com.ohgiraffers.section02.userexception.exception;
public class MoneyNegativeException extends Exception {
public MoneyNegativeException(String message) {
super(message);
}
}
package com.ohgiraffers.section02.userexception.exception;
public class NotEnoughMoneyException extends Exception {
public NotEnoughMoneyException(String message) {
super(message);
}
}
package com.ohgiraffers.section02.userexception.exception;
public class PriceNegativeException extends Exception {
public PriceNegativeException(String message) {
super(message);
}
}
3-4. 예외처리를 많이 사용하는 io 패키지와 관련한 try-catch 구문
package com.ohgiraffers.section03.uses;
import java.io.*;
public class Application1 {
public static void main(String[] args) {
/* 수업목표. 예외처리를 많이 사용하는 io패키지와 관련한 try-catch 구문을 이해할 수 있다. */
/* 설명.
* 예외 처리를 가장 많이 사용하게 되는 상황(io 패키지 관련)에서 try-catch 구문을 실제 상황과
* 유사하게 연습해 보자.
* (입출력 관련 문법을 하나하나 신경쓰기 보다는 호출 흐름에 신경 쓰자.)
* */
/* 설명. 프로젝트 바로 아래 test.dat 파일이 있다면(File 객체 생성) 해당 경로의 절대 경로를 출력 */
System.out.println(new File("test.dat").getAbsolutePath());
BufferedReader br = null; // 파일을 읽어오기 위한 통로?
try {
br = new BufferedReader(new FileReader("test1.dat"));
} catch (FileNotFoundException e) {
System.out.println("파일이 없나보네?");
} finally {
/* 필기.
* 예외처리 구문과 상관없이 반드시 수행해야 하는 경우 finally에 작성하는데
* 보통 사용한 자원(resource)을 반납할 목적으로 사용하게 된다.
* */
try {
/* 필기.
* 스트림(입출력 관련 통로)이 생성되지 않았을 때 접근해서 close() 메소드를 호출하게 되면
* NullPointerException이 발생할 수 있다. 따라서 if 문을 활용해 처리한다.
* */
if (br != null) { // NullPointerException 해결하기 위해
/* 필기.
* 입출력에서 사용한 스트림을 닫아주는 메소드이다.
* API에서 확인해보면 IOException을 throws하는 메소드이기 떄문에
* finally 블럭 안이라도 추가 예외처리를 중첩으로 해 주어야 한다.
* */
br.close();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
// 실행 결과
/Users/kimjeongmo/Desktop/한화시스템 Beyond SW/lecture/01_java/chap10-exception-lecture-source/test.dat
파일이 없나보네?
3-5. try-with-resource 구문
package com.ohgiraffers.section03.uses;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
public class Application2 {
public static void main(String[] args) {
/* 수업목표. try-with-resource 구문을 이해하고 활용할 수 있다.(feat. finally 안 쓰기) */
try (BufferedReader br = new BufferedReader(new FileReader("test.dat"))) {
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}