본문 바로가기
👨‍💻 2. 웹개발_Back end/2-1 Java

[JAVA] 10-1 예외 클래스

by 달님🌙 2021. 8. 11.
반응형

 

에러(error)와 예외(exception)의 차이 

 

- 에러(error) : 응용프로그램 실행 오류가 발생하는것

- 예외(exception) : 개발자의 잘못된 코딩으로 인해 발생되는 프로그램 자체 오류

 ( 컴파일 오류가 아니라 빨간 줄도 안나오는데 실제로 실행해보면 Exception~라고 뜨면서 프로그램이 종료되는 현상)

 

- 공통점 : 에러든 예외든 한 번 발생이 되면 프로그램이 곧바로 종료됨

- 차이점 : 예외는 예외처리라는 것을 통해 프로그램이 종료되지 않고 정상 작동할 수 있도록 유지해준다.

- 예외처리 : 코드를 보면 아무 이상이 없어 보이지만 실제로 실행해보면 논리적인 오류가 있음을 사용자가 인지하도록 오류를 명시해주는 행위

 

대표적인 예외 발생 예시 - 분모가 0일때 나눗셈

 

 

 

발생되는 오류의 종류


1.  구문오류 : 치명적이지 않음(문법 오류 : 컴파일 시 수정 가능)

int a = 10 // 세미콜론을 안붙여서 생긴 구문 오류 -> 컴파일시 오류가 뜨므로 바로 수정해주면 됨

 


 2. 논리오류 : 치명적임 (개발자의 코딩(문법적인 문제 제외) 오류, 수식계산 등..

circle_area = Math.PI * Math.pow(r, 3); 
// 원래는 Math.PI * Math.pow(r, 2) 인데 개발자가 수식을 착각해서 잘못 적음
if(Price < 0) { 	// 원래는 price > 0 이라고 써야하는데 부등호를 실수한 경우
	buy();
}

 

3. 예외 : 입력값, 입출력 관련, 물리적 장치 사용

(누군가만 혹은 어떤 컴퓨터에서만 등 어떤 특정 상황에서만 예외적으로 발생하는 오류)

 

 

예외 처리 

 

- 예외를 처리할 때는 무엇을 처리할지 어디서 처리할지 정한다.

-  예시 ) 파일 입출력 시 API 이용할 때 API 함수에서 예외를 알아서 처리하면 안됨

 

- 구조

// 메소드 내부 구조

write() throws ... {
	1. if문을 통해서 오류를 확인한다.
    - 권한이 있는지 확인
    - 파일이 존재하는지 확인
    - 파일 용량 여분이 있는지 확인
    
    2. 오류가 발생하면 보고를 해준다.
    - throw [예외 객체] 
}

 

// 관리 프로그램 구조

try { 
	write(); // 메소드 호출 
}

catch (예외이름 e) {
	발생된 예외를 사용자가 쉽게 알 수 있도록 설명해주기
}

finally {
	예외 발생 여부와 관계없이 무조건 실행되는 문
}

 

라이브러리 클래스 예외 처리 구조 알아보기

 

public class Program2 {
	public static void main(String[] args) {
		
		// 예외 처리
		try { 
			lib.write(); //write() 메소드 실행
		} catch (InterruptedException e) { // 예외 발생되면 e로 예외를 받아오기
			e.printStackTrace(); // e 예외 내용을 출력하기
		}
		System.out.println("프로그램 종료");	
	}
}
// 라이브러리 클래스
class lib { 
	public static void write() throws InterruptedException { // InterruptedException 예외가 발생하면 던지기
		Thread.sleep(1000); // 1초 대기하기
	}
}

 

 

예외처리 예제 - Calculator

 

<Calculator 클래스>

public class Calculator { // 계산기 유틸, 언제나 쓸 수 있는 라이브러리 코드임
	
	// 업무적으로 x + y 값이 1,000을 넘으면 에러 발생
	// 업무적으로 x + y 값이 음수이면 에러 발생
	public static int add(int x, int y) throws BizThousandOverException, BizNagativeNumberException  {
		int result = x + y;
		if(result >1_000) {
			throw new BizThousandOverException(); // 예외 객체를 던진다.
		}
		if(result < 0) {
			throw new BizNagativeNumberException(); // 예외객체를 여러 개 만들 수 있음
		}
		return result;
	}
	
	// 업무적으로 x - y 값이 음수이면 에러 발생
	public static int sub(int x, int y) throws BizNagativeNumberException {
		int result = x - y;
		if(result < 0) {
			throw new BizNagativeNumberException();
			}
		return result;
	}
	
	public static int multi(int x, int y) {
		int result = x * y;
		return result;
	}
	
	// 업무적으로 y의 값이 0이면 에러 발생
	public static int div(int x, int y) throws BizDivedByZeroException {
		
		if(y==0) {
			throw new BizDivedByZeroException();
		}
		int result = x / y;
		return result;
	}
}

 

1. 예외처리를 안해준 경우

 

<실행 파일>

public class ExceptionExample {
	public static void main(String[] args) { 
		// try-catch로 감싸던지 throw로 예외던지던지 하라고 빨간 줄이 생김
		System.out.println("add: " + Calculator.add(10000,-2)); // 예외 발생
	}
}

다루지않은 예외 발생 내용

- 이렇게 try-catch로 감싸던지 throw로 예외를 던지든지 하라고 빨간 줄이 생김

 

- 여기서 아무 조치도 취하지 않은 채로 그대로 실행해주면 아래와 같이 나옴

 

 

2. throw로 처리해준 경우 ( Add throws declaration 선택)

 

<실행 파일>

public class ExceptionExample {
	public static void main(String[] args) throws BizThousandOverException, BizNagativeNumberException { 
		System.out.println("add: " + Calculator.add(1,-2)); // 합이 음수가 나와서 예외 발생
		System.out.println("프로그램 정상 종료");
	}
}

 

<출력 결과>

 

<주의할 점>

- Add throws declaration 을 선택하면 또 던지는 것임

- 그런데 main 메소드는 거의 말단까지 온 경우인건데 여기서마저 던져버리면 JVM으로 떠넘기게 되는것이다.

- 그렇게 하면 아무도 예외를 처리해주지 않게 되는 것이기 때문에 Surround with try/catch 를 선택해야한다.

 

 

3-1. try-catch문 사용 (예외가 발생하지 않은 경우)

 

<예외 클래스>

public class ExceptionExample {
	public static void main(String[] args) { 

		try {
			System.out.println("add: " + Calculator.add(1,3));
		} catch (BizThousandOverException e) {
			System.out.println("결과값이 천을 넘습니다. 확인 요망!");
		} catch (BizNagativeNumberException e) {
			System.out.println("결과값이 음수입다. 확인 요망!");
		}
		
		// 무조건 마지막에 모두 들리는곳
		finally {
			System.out.println("입력값을 확인하세요~");
		}
		System.out.println("프로그램 정상 종료");
	}
}

 

<실행 파일 >

add: 4
입력값을 확인하세요~
프로그램 정상 종료

 

 

3-2. try-catch문 사용 (예외가 발생한 경우) - print 문으로 출력

 

<예외 클래스>

public class BizThousandOverException extends Exception {}
public class BizDivedByZeroException extends Exception {}
public class BizNagativeNumberException extends Exception {}

 

<실행 파일 1>

public class ExceptionExample {
	public static void main(String[] args) { 
		try {
        	System.out.println("add: " + Calculator.add(10000,-2))// add를 먼저 써주면 먼저 실행
			System.out.println("add: " + Calculator.div(10000,0)); // catch로 빠져나가므로 실행되지않음
		} catch (BizThousandOverException e) {
			System.out.println("결과값이 천을 넘습니다. 확인 요망!");
		} catch (BizNagativeNumberException e) {
			System.out.println("결과값이 음수입다. 확인 요망!");
		} catch (BizDivedByZeroException e) {
			System.out.println(e.toString());
		}
	}
}

 

<출력 결과 1>

결과값이 천을 넘습니다. 확인 요망!
입력값을 확인하세요~
프로그램 정상 종료

 

 

<실행 파일 2>

public class ExceptionExample {
	public static void main(String[] args) { 
		try {
        	System.out.println("add: " + Calculator.div(10000,0)); // div를 먼저 써주면 먼저 실행
        	System.out.println("add: " + Calculator.add(10000,-2); // catch로 빠져나가므로 실행되지않음			
		} catch (BizThousandOverException e) {
			System.out.println("결과값이 천을 넘습니다. 확인 요망!");
		} catch (BizNagativeNumberException e) {
			System.out.println("결과값이 음수입니다. 확인 요망!");
		} catch (BizDivedByZeroException e) {
			System.out.println("0으로 나눌 수 없습니다. 확인 요망!");
		}
	}
}

 

<출력 결과 2>

"0으로 나눌 수 없습니다. 확인 요망!
입력값을 확인하세요~
프로그램 정상 종료

 

 

3-3. try-catch문 사용 (예외가 발생한 경우) - toString() 메소드 출력

 

<예외 클래스> 

public class BizThousandOverException extends Exception {
	@Override 	// 메소드 오버라이딩
	public String toString() {
		return "1000을 넘을 수 없습니다.";
	}
}
public class BizNagativeNumberException extends Exception {
	@Override
	public String toString() {
		return "음수가 될 수 없습니다.";
	}
}
public class BizDivedByZeroException extends Exception {
	@Override
	public String toString() {
		return "0으로 나눌 수 없습니다.";
	}
}

 

<실행 파일>

public class ExceptionExample {
	public static void main(String[] args) { 
		try {
			System.out.println("add: " + Calculator.add(10000,-2));
			System.out.println("div: " + Calculator.div(10000,0));
		} catch (BizThousandOverException e) {
			e.printStackTrace();	
		} catch (BizNagativeNumberException e) {
			System.out.println(e.toString());
			System.out.println(e.getMessage());
		} catch (BizDivedByZeroException e) {
			System.out.println(e.toString());
		}
		// 무조건 마지막에 모두 들리는곳
		finally {
			System.out.println("입력값을 확인하세요~");
		}
		System.out.println("프로그램 정상 종료");
	}
}

 

<출력 결과>

 

1000을 넘을 수 없습니다.
	at ch10_3_example.Calculator.add(Calculator.java:10)
	at ch10_3_example.ExceptionExample.main(ExceptionExample.java:9)
입력값을 확인하세요~
프로그램 정상 종료

 

 

일반 예외 vs 실행 예외

 

1. 일반 예외 (exception)

- 컴파일러 체크 예외라고도 함

- 컴파일하는 과정에서 예외 처리 코드가 있는지 검사하고 예외 처리 코드가 없다면 컴파일 오류 발생

 

2. 실행 예외 (runtime exeption) 

- 컴파일러 넌 체크 예외라고도 함

- 예측할 수 없는 예외가 갑자기 발생할 수도 있으므로 예외 처리 코드가 있는지 검사안함

 

3. JVM 구조 설명

- 자바에서는 예외를 클래스로 관리

- JVM은 프로그램에서 예외가 발생했을 때 해당 예외 클래스를 통해 객체를 생성한다.

- 그 후 예외 처리 코드에서 예외 객체를 사용할 수 있도록 해줌

- 상위 예외 클래스 : java.lang.Exception 

- 하위 예외 클래스 : java.lang.RuntimeException(실행 예외) 와 나머지들(예를 들어 java.lang.ClassNotFoudException..)

- 하지만 RuntimeException을 부모로 상속받고 있는 클래스는 일반 예외 클래스가 아닌 실행 예외 클래스로 간주한다.

 

 

실행 예외에는 어떤 종류들이 있는지 자주 사용하는 것들을 정리해보자.

 

NullPointerException (가장 많이 발생하는 실행 예외)

 

- 의미 : 객체 참조가 없을 때 객체를 사용하려고 하면 예외가 발생

- null 값을 갖는 참조 변수로 객체 접근 연산자인 도트(.)를 사용하면 발생하는 예외이다.

 

< 에러 처리를 안한 경우>

class Person {}

public class NullPointExceptionExample {
	int x;
	int y;
	boolean b;
	Person p; // p는 Null로 초기화됨 
	
	public static void main(String[] args) {
		String data1 = "";
		System.out.println(data1.toString()); // 아무것도 출력이 안됨
		System.out.println(data1.length()); // 0
		
		String data3 = " ";
		System.out.println(data3.toString()); // 띄어쓰기가 출력됨
		System.out.println(data3.length()); // 1
		
		String data2 = null; // String data; 라고 선언(자동으로 null로 초기화됨)한 것과 같은 코드임
		System.out.println(data2.toString()); //에러 발생
		System.out.println(data2.length()); //에러 발생
		
		System.out.println("프로그램 종료");
	}
}

 

< 출력 결과>

0
 
1
Exception in thread "main" java.lang.NullPointerException
	at ch10_1_exception_class.NullPointExceptionExample_me.main(NullPointExceptionExample_me.java:21)

 

 

< 에러 처리를 한 경우>

public class NullPointerExceptionExample {
	// 필드
	int x;
	int y;
	boolean b;
	Person p; // Null로 초기화됨 
	
	// main 메소드
	public static void main(String[] args) {
		String data = null;
		
		try {
			method();
			 // 아래 코드는 null 값을 가진 객체에  접근을 시도했기 때문에 에러 발생
			System.out.println(data.toString());
			System.out.println(data.length());

		} catch(NullPointerException e) {
			System.out.println("예외 발생");
			System.out.println("e.toString(): " + e.toString());
			System.out.println("e.getMessage(): " + e.getMessage());
			System.out.println("e.printStackTrace(): ");
			e.printStackTrace();
		} catch(Exception e) {
			e.printStackTrace();
			System.out.println("예외 발생 코드 실행!");
		}
		System.out.println("프로그램 종료");
	}
	
	// 메소드 선언
	private static void method() {}
}

 

< 출력 결과>

예외 발생
e.toString(): java.lang.NullPointerException
e.getMessage(): null
e.printStackTrace(): 
java.lang.NullPointerException
프로그램 종료
	at ch10_1_exception_class.NullPointerExceptionExample.main(NullPointerExceptionExample.java:17)

- data 변수는 null 값을 가지고 있어서 String 객체를 참조하지 않는다.

- 그런데 코드를 보면 String 객체에 소속된 toString() 메소드를 호출하면서 접근을 시도했다. 따라서 에러가 발생

 

 

ArrayIndexOutOfBoundsException

 

- 의미 : 배열에서 인덱스 범위를 초과할 경우 발생하는 예외

- 범위를 초과해야만 발생하는게 아니라 실행 매개값을 받기로 했는데 매개값을 입력해주지 않은 경우도 예외 발생 

- 이클립스에서 argument 매개값 받아 실행하는 방법

  [Run] > [Run Configurations] > [Arguments] > 매개값 입력 > [Apply] > [Run]

 

<2개의 매개값이 잘 입력된 경우>

 

<2개가 아닌 한 개의 매개값만 입력된 경우>

 

<예외 처리는 아니지만 비슷한 기능을 가진 if문 실행>

public class ArrayIndexOutOfBoundsExceptionExample_Handling {
	public static void main(String[] args) {
		if(args.length == 2) {
			String data1 = args[0];
			String data2 = args[1];
			
			System.out.println("args[0]: " + data1);
			System.out.println("args[1]: " + data2);
		}
		else {
			System.out.println("두 개의 실행 매개값이 필요합니다.");
		}
	}
}

 

 

NumberFormatException

 

- 의미 : 문자열 데이터를 숫자로 변경(파싱)할 때 숫자로 변환될 수 없는 문자가 포함되어있는 경우 발생하는 예외

- 포장 클래스 : Integer, Double

- 문자열을 숫자로 바꿔주는 메소드 : ParseInt(), ParseDouble()

- 자세한건 11장에서 배울 예정 

 

<예외 코드가 포함된 클래스>

public class NumberFormatExceptionExample {
	public static void main(String[] args) {
		
		String data1 ="200";
		String data2 ="a300";
	
		int value1 = Integer.parseInt(data1);
		int value2 = Integer.parseInt(data2);
		int result = value1 + value2;
		
		System.out.printf("%d + %d = %d\n", value1, value2, result);
	}
}

 

<예외 발생 결과 창>

- a300 이라는 문자열을 숫자로 변환할 수 없기 때문에 예외가 발생함

 

 

ClassCastException

 

- 의미 : 클래스의 타입 변환은 모든 클래스 관계에서 가능한 것이 아니라 가능한 경우들이 몇 개 정해져 있는데 

  만약 클래스 타입 변환이 안되는 경우 타입 변환을 시도한 경우 이 예외가 발생한다.

- ClassCastException 예외를 발생시키지 않으려면 먼저 instanceof 연산자로 타입변환이 가능한지 확인해보는것이 좋다.

 

<예외 코드가 포함된 클래스  및 예외 발생 결과 창>

 

<instanceof 연산자 사용>

 

 

예외 처리 추가 예제 - 두 수 (정수) 입력받아 나눗셈 실행

 

< 에러 처리를 안한 경우>

import java.util.Scanner;

public class ExceptionCase {
	public static void main(String[] args) {
		// 두 정수를 사용자로부터 입력받기 예) 3,4 3/4 나누기 실행하고
		// 입력받은 숫자와 나누기 결과를 출력하시오
		
		Scanner sc = new Scanner(System.in);
		
		System.out.print("a / b 식에 사용될 a 값을 입력하세요 > ");
		int n1 = sc.nextInt();
		
		System.out.print("a / b 식에 사용될 b 값을 입력하세요 > ");
		int n2 = sc.nextInt();
		
		System.out.printf("%d / %d = %d\n", n1, n2, n1/n2);
	}
}
a / b 식에 사용될 a 값을 입력하세요 > 3
a / b 식에 사용될 b 값을 입력하세요 > 0
Exception in thread "main" java.lang.ArithmeticException: / by zero
	at ch10_2_exception_handling.ExceptionCase.main(ExceptionCase.java:19)

 

< 에러 처리를 한 경우>

import java.util.Scanner;

public class ExceptionCase {
	public static void main(String[] args) {
		// 두 정수를 사용자로부터 입력받기 예) 3,4 3/4 나누기 실행하고
		// 입력받은 숫자와 나누기 결과를 출력하시오
		
		Scanner sc = new Scanner(System.in);
		
		System.out.print("a / b 식에 사용될 a 값을 입력하세요 > ");
		int n1 = sc.nextInt();
		
		System.out.print("a / b 식에 사용될 b 값을 입력하세요 > ");
		int n2 = sc.nextInt();
		
		try {
			System.out.printf("%d / %d = %d\n", n1, n2, n1/n2);
			
		} catch(ArithmeticException e) {
			System.out.println("0으로 나눌 수 없습니다.");
		} finally {
			System.out.println("프로그램 종료");
		}
	}
}
a / b 식에 사용될 a 값을 입력하세요 > 3
a / b 식에 사용될 b 값을 입력하세요 > 0
0으로 나눌 수 없습니다.
프로그램 종료

 

 

예외 처리 추가 예제 - 두 수 (실수) 입력받아 나눗셈 실행 + 문자열 예외처리

 

 - 이번에는 위 예제에서 두 가지 기능을 추가할 것이다.

1. 문자열을 입력받은 경우 예외 처리

2. 실수를 입력받아도 코드가 실행되도록 변경 )  

 

- 처리해준 후 한 가지 문제점이 발생하는데, Double 타입끼리 나눗셈을 해주면 0으로 나눈 경우

원래는 예외가 발생했던게 이제는 infinity라는 값을 출력해준다는 점이다.

따라서 이것도 예외로 만들어주기위해서는 if문을 사용하여 나눈 값이 double인지 여부를 확인하여

해당 예외 객체로 던지는 코드를 작성해주면 그 예외가 있는 catch 블록으로 가게 된다.

 

// java.lang에 있는건 그냥 쓸 수 있지만 java.util에 있는건 import 필수임
import java.util.InputMismatchException;
import java.util.Scanner; 

public class ExceptionCase_StringAndDouble {
	public static void main(String[] args) {
		// 두 정수를 사용자로부터 입력받기 예) 3,4 3/4 나누기 실행하고
		// 입력받은 숫자와 나누기 결과를 출력하시오
		
		Scanner sc = new Scanner(System.in);
		
		// 예외 출력 결과 클릭하면 몇번째 라인에서 예외가 발생한건지 알려준다.
		// 그 코드는 예외 발생 가능성이 있는 코드이므로 catch 안으로 넣어준다.
		
		try {
			System.out.print("a / b 식에 사용될 a 값을 입력하세요 > ");
			double n1 = sc.nextDouble();
			
			System.out.print("a / b 식에 사용될 b 값을 입력하세요 > ");
			double n2 = sc.nextDouble();
			
			if(Double.isInfinite(n1/n2)) {
				throw new ArithmeticException(); 
				// infinity가 나오는 현상 해결 - 예외로 출력하고싶다면 그 예외 객체를 throw 하기
			}
			
			System.out.printf("%f / %f = %f\n", n1, n2, (double)n1/n2);
		
		} catch(InputMismatchException e) {
			System.out.println("문자열로 나눌 수 없습니다.");
		} catch(ArithmeticException e) {
			System.out.println("0으로 나눌 수 없습니다.");
		} finally {
			System.out.println("프로그램 종료");
		}
	}
}

 

 

반응형

댓글