11_예외처리(Exception)
프로그램 에러
프로그램 수행 시 치명적 상황이 발생하여 비정상 종료 상황이 발생한 것
*예외 클래스 계층 구조 (== 상속)
Exception과 Error 클래스 모두 Object 클래스의 자손이며 모든 예외의 최고 조상은 Exception 클래스
반드시 예외 처리해야 하는 Checked Exception과 해 주지 않아도 되는 Unchecked Exception으로 나뉨
=> 시스템 오류는 코드상의 잘못이 아니니까 예외 처리 불가능
* 에러(오류) 종류
- 시스템 에러: 컴퓨터의 오작동으로 인해 발생하는 에러
=> 소스코드로 해결이 안 됨, 개발자의 코드 잘못이 아닌 컴퓨터 문제(심각한 에러)
- 컴파일 에러: 소스코드상의 문법적인 문제로 발생하는 에러
=> 소스코드 수정으로 해결 가능, 개발자의 코드 잘못(이클립스가 애초에 빨간 밑줄로 알려 주는 오류)
- 런타임 에러: 소스코드상으로는 문제가 없는데 프로그램 실행 도중 발생하는 에러, 주로 if문 사용으로 인한 에러 처리
=> 사용자의 잘못일 수도 있고, 개발자가 예측 가능한 경우를 제대로 처리 안 해 놓은 잘못일 수도 있음
- 논리 에러: 소스코드상의 문법적인 문제도 없고 실행했을 때도 굳이 문제가 되진 않지만 내가 짠 프로그램 의도상 반대로 작동하는 에러
=> 시스템 에러를 제외한 컴파일 에러, 런타임 에러, 논리 에러와 같은 비교적 덜 심각한 것들을 가지고 작업
== 소스 수정으로 해결 가능한 에러
이런 것들을 "예외(Exception)"라고 함
=> 이러한 "예외"들이 "발생"했을 경우에 대비해서 미리 "처리"하는 방법을 정의해 두는 것을 "예외처리"라고 함
*예외처리를 하는 이유: 예외 발생 시 프로그램이 비정상적으로 종료되는 것을 막기 위함! 반드시 해야 함!
*예외처리 방법
1. try ~ catch문을 이용
2. throws를 이용(떠넘기기, 위임하기)
// 충분히 예측 가능한 오류들(RuntimeException 자식 클래스들) 에 대한 예외처리: UncheckedException
* RuntimeException
- 프로그램 실행 시 발생되는 예외들
*RuntimeException의 자식 클래스들
- ArraysIndexOutOfBoundsException: 배열의 부적절한 인덱스로 접근할 때 발생하는 예외
- NegativeArraySizeException: 배열의 크기를 음수로 지정할 경우 발생하는 예외
- ClassCastException: 다형성이 적용된 상황에서 형 변환을 잘못했을 때 발생하는 예외
- NullPointerException: 참조변수값이 아직 null임에도 불구하고 접근하려고 할 때 발생하는 예외
- ArithmethicException: 나누기 연산 시 0으로 나눌 때 발생하는 예외
- ...
=> 이러한 RuntimeException과 관련된 예외들은 충분히 예측 가능하기 때문에
예외 자체가 애초에 발생이 안 되게끔 "조건문"으로 해결 가능하긴 함 (선처리) => if문 권장
굳이 예외처리 (예외가 발생했을 때 실행할 내용을 정리해 두는 후처리) 를 할 필요가 없음 => 후처리는 try ~ catch문
* try ~ catch문
[ 표현법 ]
try {
실행할 코드 (예외가 발생될 법한 구문이 포함되어 있음);
} catch(발생할예외클래스명 변수명) {
해당 예외가 발생할 경우 실행할 구문;
}
A_UncheckedException 클래스
package com.kh.exception;
import java.util.InputMismatchException;
import java.util.Scanner;
// 충분히 예측 가능한 오류들에 대한 예외처리: UncheckedException
public class A_UncheckedException {
// 스캐너 객체를 전역변수로 세팅
// => 이 클래스 내부의 모든 메소드에서 가져다 쓸 수 있게끔 해 줄 것
Scanner sc = new Scanner(System.in);
public void method1() {
// ArithmeticException: 나누기 연산 시 0으로 나눌 때 발생하는 예외
// 사용자로부터 두 개의 정수값을 입력받아 나눗셈 연산을 출력
System.out.print("첫 번째 정수: ");
int num1 = sc.nextInt();
sc.nextLine();
System.out.print("두 번째 정수(0 제외): ");
int num2 = sc.nextInt();
sc.nextLine();
// int result = num1 / num2;
// System.out.println("나눗셈 연산 결과: " + result);
// 해결방법1. 조건문으로 처리(애초에 예외 자체가 발생하지 않게끔 if문으로 조건 검사 후 계산 진행)
// => 예외 처리가 아님
/*
if(num2 != 0) {
int result = num1 / num2;
System.out.println("나눗셈 연산 결과: " + result);
}
else {
System.out.println("0으로 나눌 수 없습니다.");
}
System.out.println("프로그램 종료");
*/
// 해결방법2. 예외처리 구문으로 해결
// => 예외가 발생했을 경우를 대비해서 실행할 내용을 정의해 두는 것
/*
* try ~ catch문
*
* [ 표현법 ]
* try {
* 실행할 코드 (예외가 발생될 법한 구문이 포함되어 있음);
* } catch(발생할예외클래스명 변수명) {
* 해당 예외가 발생할 경우 실행할 구문;
* }
*/
try {
int result = num1 / num2; // 오류가 생기면 이 구문에서 멈춤
System.out.println("나눗셈 연산 결과: " + result); // 오류가 생기면 이 구문 실행 X
} catch(ArithmeticException e) { // 예외클래스명과 같은 오류라면
System.out.println("0으로 나눌 수 없습니다."); // 이 구문을 실행함 O (== 예외처리)
e.printStackTrace(); // 오류를 추적할 수 있음
// 현재 예외가 어디서 발생했는지 알려 줌
// 오류 내용
// java.lang.ArithmeticException: / by zero
// at com.kh.exception.A_UncheckedException.method1(A_UncheckedException.java:75) // 읽는 순서 두 번째: 얘 때문이야
// at com.kh.exception.run.ExceptionRun.main(ExceptionRun.java:34) // 읽는 순서 첫 번째: 이거 호출할 때 오류가 났어
}
System.out.println("프로그램 종료"); // 예외처리 후 이 구문 실행 O
// => if문은 선처리, try ~ catch문은 후처리이기 때문에 if문을 더 권장하기는 함!
}
public void method2() {
System.out.println("정수 입력 (0제외): ");
try {
int num = sc.nextInt(); // InputMismatchException이 발생할 법한 구문(정수 이외의 값을 입력할 경우)
sc.nextLine();
int result = 10 / num; // ArithmeticException 이 발생할 법한 구문
System.out.println("나눗셈 결과: " + result);
} catch(ArithmeticException e) {
System.out.println("0으로 나눌 수 없습니다.");
} catch(InputMismatchException e) {
System.out.println("정수로 제대로 입력해야 합니다.");
} // 다중 catch 블럭: 예외가 여러 개 발생할 가능성 ~~~~~~~~~~~
System.out.println("프로그램 종료");
}
public void method3() {
// ArrayIndexOutOfBoundException: 배열의 부적절한 인덱스로 접근할 때 발생하는 예외
// NegativeArraySizeException: 배열의 크기를 음수로 지정할 경우 발생하는 예외
System.out.print("배열의 크기를 입력하세요: ");
int size = sc.nextInt();
sc.nextLine();
// if문을 이용한 방법
/*
if(size > 101) {
int[] arr = new int[size]; // NagativeArraySizeException이 발생할 법한 구문 (사용자가 음수를 입력할 수도 있음)
System.out.println("100번 인덱스 값: " + arr[100]); // ArrayIndexOutOfBoundsException이 발생할 법한 구문(사용자가 100 이하의 값을 입력할 수도 있음)
}
System.out.println("프로그램 종료");
*/
// try ~ catch문을 이용한 방법
/*
try {
int[] arr = new int[size];
System.out.println("100번 인덱스 값: " + arr[100]);
} catch (NegativeArraySizeException e) {
System.out.println("배열의 크기로는 음수를 제시할 수 없습니다.");
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("부적절한 인덱스로 접근했습니다.");
}
System.out.println("프로그램 종료");
*/
/*
try {
int[] arr = new int[size];
System.out.println("100번 인덱스값: " + arr[100]);
} catch (RuntimeException e) { // 다형성 적용해서 NegativeArraySizeException, ArrayIndexOutOfBoundsException의
// 부모 타입인 RuntimeException을 이용해서 예외 클래스 처리 가능
System.out.println("예외가 발생하긴 했음... 배열의 크기를 음수로 입력했거나 부적절한 인덱스로 접근했음...");
System.out.println("프로그램 종료할게...");
// 다중 catch 블록이 줄어서 편하긴 하지만
// 어떤 오류가 발생했는지 명확하게(디테일하게) 파악할 수 없는 단점이 있음
*/
try {
int[] arr = new int[size];
System.out.println("100번 인덱스 값: " + arr[100]);
} catch (NegativeArraySizeException e) {
System.out.println("배열의 크기로는 음수를 제시할 수 없습니다.");
} catch (RuntimeException e) {
System.out.println("예외가 발생하긴 했음");
}
// 다형성을 적용해서 부모 예외 클래스와 자식 예외 클래스의 catch블럭을 다중으로 작성하려면
// => 범위가 작은 자식 타입의 예외 클래스를 먼저 기술해야 함
}
}
RuntimeException 관련된 예외는
- 조건문으로 해결 가능: 예외 자체가 발생 안 되게끔 개발자가 소스 코드로 핸들링 가능(예외 처리가 아님!!)
=> 예방의 개념
- 예외처리구문으로 해결 가능: 예외가 발생했을 때를 대비해서 그때 실행할 내용을 정의해 두는 것
=> 수습의 개념
예측이 가능한 상황: 조건문으로 해결(더 권장하는 방법)
예측이 불가능한 상황: 무조건 예외처리구문으로 해결할 수밖에 없음
=> RuntimeException 계열은 모두 충분히 예측 가능한 상황이기 때문에
조건문으로 해결하는 것을 권장함
즉, 예외처리구문을 작성하는 것이 필수는 아님 => "Unchecked Exception"
// 예측 불가능한 예외들(IOException의 자식 클래스들)에 대한 예외처리: CheckedException
*CheckedException은 반드시 예외처리를 해야 하는 예외들
(즉, 예측 불가한 곳에서 발생하기 때문에 미리 예외 처리 구문을 작성해야 함)
=> 주로 사람이 아닌 외부 매체와 어떤 "입출력"이라는 과정이 나타날 때 주로 발생함
B_CheckedException 클래스
package com.kh.exception.controller;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
// 예측 불가능한 예외들(IOException의 자식 클래스들)에 대한 예외처리: CheckedException
public class B_CheckedException {
public void method1() throws IOException { // 얘도 떠넘긴다면? method1의 예외처리는 method1을 호출한 b.method1(); (== Run클래스의 메인 메소드)가 예외처리 해야 함
/*
try {
method2();
} catch (IOException e) {
System.out.println("예외 발생됨"); // method2가 떠넘긴 예외 처리를 반드시 이렇게 try ~ catch문으로 받아 줘야 함
}
*/
method2(); // public void method1() "throws IOException" { // 적는 순간 빨간 밑줄 사라짐
}
// throws 방법
public void method2() throws IOException {
// Scanner 와 같이 키보드로 값을 입력받을 수 있는 객체 (단, 이 객체는 문자열로만 생성 가능 == 겁나 옛날 버전이라서)
// => IOException이 발생할 법한 상황 연출을 위함임
BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); // import
System.out.print("아무 문자열이나 입력하세요: ");
// String str = br.readLine(); // 문자 한 줄 단위로 입력받겠다
// 이 메소드를 호출 시 IOException가 발생할 수도 있음을 컴파일 에러로 알려 줌(빨간 밑줄)
// => 빨간 밑줄 뜨면 실행조차 안 됨!
// 즉, 실행하고 싶으면 예외 처리 하고 실행시켜라!
// 1. try ~ catch문: 예외가 발생할 법한 코드가 있는 이 자리 그대로 바로 여기에서 예외를 처리하겠음
/*
try {
String str = br.readLine();
System.out.println("문자열의 길이: " + str.length());
} catch(IOException e) { // import 해 주기
// IOException이 언제 발생될지 모름
System.out.println("예외 발생됨");
}
*/
// 2. throws: 떠넘기기, 위임하기
// 지금 여기서 예외를 바로 처리하지 않고 현재 이 메소드를 호출한 곳으로 떠넘기겠다라는 뜻
String str = br.readLine(); // public void method2() "throws IOException" { // 를 추가하는 순간 이 오류를 method2()를 호출한 method1()에게 떠넘기게 됨
// 즉, be.readLine()에서의 빨간 밑줄은 사라지고 method1에 빨간 밑줄이 생김!
System.out.println("문자열의 길이: " + str.length());
}
}
ExceptionRun 클래스
package com.kh.exception.run;
import java.io.IOException;
import com.kh.exception.controller.A_UncheckedException;
import com.kh.exception.controller.B_CheckedException;
public class ExceptionRun {
// main 메소드에서 throws로 예외 처리를 떠넘기게 되면 JVM에 의해 예외 처리가 됨!
// JVM이 어떻게 예외처리를 해 줄지 모르기 때문에 내가 직접 try ~ catch문으로 예외처리하는 것을 권장함
public static void main(String[] args) throws IOException { // JVM이 알아서 처리해 줌...
A_UncheckedException a = new A_UncheckedException();
// a.method1();
// a.method2();
// a.method3();
B_CheckedException b = new B_CheckedException();
// b.method1();
/*
try {
b.method2();
} catch (IOException e) {
System.out.println("예외 발생됨"); // method1이 떠넘긴 예외 처리를 반드시 이렇게 try ~ catch문으로 받아 줘야 함
}
*/
// 계속 계속 떠넘기다가 여기서도 예외 처리 하기 싫으면??
b.method2();
}
}
IOExceotion throws 하는 법
public void 클래스명 throws IOException {
위 클래스 기준 throws 순서
method2() -> throws -> method1() -> throws -> br.method1() -> throws -> JVM이 알아서 처리