1. 예외처리(Exception)

→ 오류 발생 가능성이 있는 부분에 대한 처리를 미리 프로그래밍 해주는 것

 

1) 컴파일러  체크 예외 (Checked Exception)

- 자바 소스를 컴파일하는 과정에서 예외 처리 코드를 검사하여 예외 처리 코드가 없다면 컴파일 오류 발생

- 실행하지 않고도 오류가 발생한 것을 알 수 있음

- Checked Exception은 필요하지 않다고 결론이 남

 

2) 실행 예외 (Runtime Exception)

- 컴파일하는 과정에서 예외처리 코드를 검사하지 않는 예외

- 개발자들 사이에서 실행 예외만 신경쓰기로 합의, 오로지 개발자의 경험에 의해 처리

- 실행 예외에 대한 예외처리 코드를 넣지 않았을 때 해당 예외가 발생하면 프로그램 즉시 종료

 

RuntimeException을 상속받는 예외들은 모두 Unchecked Exception이다.

둘 다 확인 시점은 실행 단계이고, rollback 여부는 설정에 따라 달라진다.

 

 

2. 실행 예외 (Unchecked Exception = Runtime Exception)

1) NullPointerException

→ 객체 참조가 없는 상태, null 값을 갖는 참조 변수로 객체 접근 연산자인 "."을 사용했을 때 발생

public class NullPointerEx {
	public static void main(String[] args) {
		String str = null;
		//str = "HELLO";
		
		// .toLowercase()는 모든 문자를 소문자로 만들어줌
		System.out.println(str.toLowerCase());
	}
}

참조형 변수로 선언한 str이 null값이므로 위와 같은 오류가 발생하는 것을 확인할 수 있다.

 

2) ArrayIndexOutOfBoundsException

→ 배열에서 인덱스 범위를 초과하여 사용할 경우 발생

public class ArrayIndexEx {
	public static void main(String[] args) {
		int [] arr = {3, 6, 9};
		
		// 있지도 않은 인덱스 번호 조회하기, 그러나 문법상 오류는 없음
		System.out.println(arr[3]);
	}
}

배열의 길이가 3인데 존재하지 않는 3번 인덱스 값을 조회했으므로 위와 같은 오류가 발생하는 것을 확인할 수 있다. 

 

3) NumberFormatException

→ 문자열로 되어 있는 데이터를 숫자로 변경하는 경우 발생

public class NumberFormatEx {
	public static void main(String[] args) {
		String a = "35";
		String b = "21";
		System.out.println(a + b);
		
		// str -> int 변환
		int i = Integer.parseInt(a); // 문자 35를 숫자 35로 변환
		int j = Integer.parseInt(b); // 문자 21을 숫자 21로 변환 
		System.out.println(i + j);
		
		// parseInt는 문자열 내부에 순수한 정수가 들어있어야 변환을 실행하며 
		// 정수값이 아니라면 NumberFormatException이 발생
		String s = "Hello";
		System.out.println(Integer.parseInt(s));
	}
}

s 변수는 String 타입으로 정수값이 아닌데 parseInt()는 문자열 내부에 순수한 정수가 있어야함 변환을 실행하기 때문에 위와 같은 오류가 발생하는 것을 확인할 수 있다.

 

4) ClassCastException

→ 상속 관계나 인터페이스 관계가 없는 클래스들을 억지로 형 변환할 경우 발생

☆ 형 변환은 부모클래스 -자식클래스, 구현클래스-인터페이스 간에 발생  이 관계가 아니라면 다른 클래스로 타입 변환 불가

//하나의 클래스 파일에 2개 이상의 클래스 선언 가능 (자주 사용하지는 않음)
// 상속관계 : 부모 Animal을 상속한 자식 Dog, Cat
class Animal{}
class Dog extends Animal{}
class Cat extends Animal{}

public class ClassCastEx {
	public static void main(String[] args) {
		Dog d = new Dog();
		Animal da = d;
		d = (Dog)da;
		
		System.out.println("타입 변환 성공 : Animal -> Dog");
		
		Animal c = new Cat();
		Dog d2 = (Dog)c; // Cat은 Dog 타입으로 변환 불가
		
		System.out.println("타입 변환 성공? : Dog -> Cat");
	}
}

 

Cat과 Dog는 상속 관계에 있는 클래스가 아니므로 위와 같은 오류가 발생하는 것을 확인할 수 있다.

 

 

3. 예외처리코드

1) try ~ catch

- 예외 발생 시 갑작스러운 종료를 막고 정상 실행을 유지할 수 있도록 처리하는 코드

- try~catch~finally 블록 : 생성자나 메서드 내부에서 작성되어 컴파일 예외나 실행 예외가 발생할 경우 예외처리

- try 블록 : 예외 발생 가능성이 있는 코드 작성 예외 발생 시 실행을 멈추고 catch 블록으로 이동

- finally 블록 : 예외 발생 여부와 상관없이 항상 실행 → 필수는 X, 로직이 블럭과 관련 있을 시 작성

※ finally 구문이 실행되지 않는 경우

① finally 구문 이전에 System.exit() 구문 호출 시

② 컴퓨터가 꺼져서 시스템이 멈추었을 시

③ finally 블록 내부에서 예외가 발생했을 시

public class TryCatchEx1 {
	public static void main(String[] args) {
		int i = 10;
		int j = 5;
		
		try { // 예외가 발생할 가능성이 있는 코드를 넣는 구역
			System.out.println(i / j); // 예외 발생 가능성이 있음
			System.out.println("예외 발생하지 않을 때만 실행됨");
		} catch(Exception e) { //catch 블럭에는 Exception의 종류를 기입
			System.out.println("0으로 나눠서 catch 블럭으로 넘어왔습니다.");
		} finally { // try, catch 둘 중 어느 블럭이라도 실행되면 마무리 블럭 실행
			System.out.println("어쨌든 잘 마무리 했습니다.");
		}
	}
}

변수 j가 5라면 try 블럭 안의 내용과 finally 블럭 내용이 출력된다.

변수 j가 0이라면 예외가 발생하여 catch 블럭으로 넘어가고 해당 내용과 finally 블럭 내용이 출력된다.

 

2) 다중 catch

- 여러 가지 예외가 발생한다면 다중 catch 블록 작성

- 상위 예외 클래스가 하위 예외 클래스보다 아래쪽에 위치해야 함

- 상위 예외 클래스의 catch 블록이 위에 있다면 하위 예외 클래스의 catch 블록은 실행되지 않음

- 자바 7 버전부터 하나의 catch 블록에서 여러 개의 예외 처리 가능

- catch() 괄호 안에 동일하게 처리하고 싶은 예외를 |로 연결 (※ 두 예외가 상속 관계에 있으면 안됨)

ublic class MultiCatchEx {
	public static void main(String[] args) {
		String data1 = "30";
		String data2 = "11";
		
		try {
			// NumberFormatException 발생 가능 (정수가 아닌 값이 들어갈 경우)
			int i = Integer.parseInt(data1);
			int j = Integer.parseInt(data2);
		
			// ArithmeticException 발생 가능 (j에 0이 들어갈 경우)
			int result = i / j;
			System.out.println("i / j = " + result);
		
			// NullPointerException 발생 가능
			String str = null;
			str.charAt(0); // 0번째 문자 얻기인데 null값인 경우
		} catch(NumberFormatException | NullPointerException e) {
			System.err.println("데이터를 숫자만 넣어주세요.");
			System.err.println("혹은 문자를 제대로 만들어주세요.");
		} catch(ArithmeticException e) {
			System.err.println("0으로 나눌 수 없습니다.");
		} catch(Exception e) { // 범용 에러 처리 (대부분의 에러를 다 커버)
			System.err.println("알 수 없는 에러가 발생했습니다.");
			System.err.println("복구중입니다.");
		}
	}
}

catch 블록은 위에서부터 차례대로 검색되고, 예외 발생 시 해당하는 예외처리를 할 수 있게 된다.

위 예제의 경우 NullPointerException 예외만 발생하므로 첫번째 catch 블록의 내용만 출력하는 것을 확인할 수 있다.

 

3) throws

- 메서드나 생성자를 호출한 곳으로 예외를 던지는 방법 → 예외 처리를 직접 수행하지 않고 메서드 호출자에게 던짐

- try 블록 내부에서 호출되어야 하고, catch 블록에서 떠넘겨 받은 예외 처리를해야 함

- throws가 붙어있는 메서드는 반드시 try 블록 내부에서 호출되어야 하고, catch 블록에서 떠넘겨 받은 예외를 처리해야 함

- main() 메서드에서 throws를 사용하는 것은 예외처리를 JVM에게 넘기겠다는 의미

  ※ JVM은 예외를 직접 처리해주지 않고 예외 메시지만 출력하고 프로그램 종료

public class ThrowsEx {
	public static String[] greetings = {"안녕", "싸왓디", "헬로"};
	
	// 예외의 원인이 메서드 선언부가 아닌 호출부에 있을 경우
	// 메모리 영역이 다르므로 예외처리를 메서드 호출지역으로 떠넘겨줘야 함
	// 이를 throws라고 하고, 메서드 혹은 생성자 호출 시 예외처리를 강요할 때 사용
	public static void greet(int idx) {
		try {
			System.out.println(greetings[idx]);
		} catch(ArrayIndexOutOfBoundsException e){
			// 코드 안적어도됨
		}
		
	}
	
	public static void main(String[] args) {
		// throws가 붙어 있는 메서드나 생성자 호출 시에는
		// 해당 메서드를 try 블록 내부에서 호출해야 예외처리를 진행해줌
		greet(3);
	}
}

위 예제에서 greetings 배열의 크기가 3인데 4번째 인덱스를 조회하고 있으므로 ArrayIndexOutOfBoundsException이 발생하고, 이 경우 위와 같이 greet 함수에서 예외처리를 해줄 수 있다. 하지만, 객체 지향 측면에서 해당 메서드의 책임이 많아지므로 위 코드는 좋은 코드가 아니다. 

public class ThrowsEx {
	public static String[] greetings = {"안녕", "싸왓디", "헬로"};
	
	// 예외의 원인이 메서드 선언부가 아닌 호출부에 있을 경우
	// 메모리 영역이 다르므로 예외처리를 메서드 호출지역으로 떠넘겨줘야 함
	// 이를 throws라고 하고, 메서드 혹은 생성자 호출 시 예외처리를 강요할 때 사용
	
	// 해당 예외가 발생하면 호출부(여기서는 main)에게 처리를 떠넘기는 것				
	public static void greet(int idx) throws Exception {
		System.out.println(greetings[idx]);
	}
	
	public static void main(String[] args) {
		// throws가 붙어 있는 메서드나 생성자 호출 시에는
		// 해당 메서드를 try 블록 내부에서 호출해야 예외처리를 진행해줌
		try {
			greet(3);
		} catch(Exception e) {
			// .printSackTrace()는 예외발생 경로를 추적하는 메세지 출력
			// 주로 개발 과정에서 예외의 원인을 역추적할 때 유용
			e.printStackTrace();
		}
		System.out.println("프로그램 정상 종료!");
	}
}

위와 같이 throws가 붙은 greet() 메서드 호출 시 해당 메서드가 호출되고 있는 메인함수 쪽에서 예외처리를 진행할 수 있도록 하는 것이 바람직하다.

위와 같이 예외처리가 잘 이루어지고, 프로그램이 정상 종료된 것을 확인할 수 있다.

 

public class ThrowsEx2 {
	public void aaa(int n) throws Exception {
		System.out.println("aaa 호출");
		int i = 10 / n;
		System.out.println("계산 결과 : " + i);
		System.out.println("aaa 실행 중");
	}
	
	public void bbb() throws Exception {
		System.out.println("bbb 호출");
		aaa(0);
		System.out.println("bbb 실행 중");
	}
	
	public void ccc() throws Exception {
		System.out.println("ccc 호출");
		bbb();
		System.out.println("ccc 실행 종료");
	}
	
	public ThrowsEx2() throws Exception {
		System.out.println("생성자 호출");
		ccc();
		System.out.println("생성자 실행 종료");
	}
	
	public static void main(String[] args) {
		try {
			ThrowsEx2 te = new ThrowsEx2();
		} catch (Exception e) {
			e.printStackTrace();
		}
		System.out.println("진짜 코드 호출 끝!");
	}
}

 

4) throw

- 예외를 강제로 발생시키려면 throw 키워드 이용 → 개발자가 직접 예외 클래스를 정의하여 만들 수 있음

public class ThrowEx {
	public static int calcSum(int n) throws Exception {
		// 프로그램이 throw 구문을 만나는 순간 예외를 즉시 발생시키고,
		// 해당 예외를 처리해줄 catch 블록이 있는지 검색
		if(n <= 0) {
			throw new Exception(); // 예외도 클래스로 정의되기 때문에 인스턴스 생성
		}
		int sum = 0;
		for(int i = 1; i <= n; i++) {
			sum += i;
		}
		return sum;
	}
	
	public static void main(String[] args) {
		try {
			int result1 = calcSum(100);
			System.out.println("1~100까지의 누적합 : " + result1);
			
			int result2 = calcSum(-100);
			System.out.println("1~100까지의 누적합 : " + result2);
		} catch (Exception e) {
			e.printStackTrace();
			System.err.println("매개값을 양수로 전달해주세요.");
		}
	}
}

throw 키워드가 없다면 위 코드에서 논리적으로는 문제가 없기 때문에 정상적으로 작동한다.

하지만, 반복문 사용 시 i 값에 음수가 들어간다면 반복문을 실행하지 않기 때문에 위 예시에서는 throw를 사용하여 강제로 예외처리를 해주는 것이 좋다.

객체 생성 시 throw 키워드를 붙여주면 위와 같이 강제로 설정한 예외처리를 해주는 것을 확인할 수 있다.

 

5) 사용자 정의 예외

- 자바 표준 API에서 제공하는 예외 클래스만으로 다양한 종류의 예외를 표현할 수 없는 문제가 발생

- 일반 예외로 선언할 경우 Exception 클래스 상속, 실행 예외로 선언할 경우 RuntimeException 클래스 상속

- 사용자 정의 예외 클래스의 이름은 Exception으로 끝나는 것이 좋음

// 잔고 불충분 예외
// 사용자 정의 예외 클래스 생성 시 Exception 클래스나 RuntimeException 클래스 상속
public class BalanceInsufficientException extends RuntimeException {
	// 일반적으로 사용자 정의 예외 클래스를 만들 때는 
	// 기본 생성자와 예외원인 메세지를 받는 생성자를 두 개 오버로딩하여 선언만 해줌
	public BalanceInsufficientException() {};
	
	public BalanceInsufficientException(String message) {
		super(message);
	}
}
// 음수값 입금 예외
public class DepositMinusMoneyException extends RuntimeException {
	public DepositMinusMoneyException() {}
	
	public DepositMinusMoneyException(String message) {
		super(message);
	}
}
public class Account {
	private long balance;
	
	public long getBalance() {
		return this.balance;
	}
	
	// 음수로 입금할 경우 강제 예외처리
	public void deposit(int money) throws DepositMinusMoneyException {
		if (money < 0) {
			throw new DepositMinusMoneyException("음수로 입금할 수 없습니다.");
		}
		this.balance += money;
	}
	
	// 인출 시 잔액이 부족한 경우 강제 예외처리
	public void withdraw(int money) throws BalanceInsufficientException {
		if(this.balance < money) {
			throw new BalanceInsufficientException("잔고가 부족합니다.");
		}
		this.balance -= money;
	}
}
public class MainClass {
	public static void main(String[] args) {
		Account acc = new Account();
		
		try {
			acc.deposit(100000);
			System.out.println("입금 후 잔액 : " + acc.getBalance() + "원");
			acc.withdraw(100000);
		} catch(BalanceInsufficientException e) {
			// 예외 클래스가 제공하는 getMessage() 메서드는 예외의 원인 메세지를 String 타입으로 리턴
			// 자바 표준 API에서 제공하는 다양한 예외클래스들은 각각의 예외 원인 메세지가 기본적으로 객체 안에 저장
			e.printStackTrace();
			// 생성자에서 제공해준 메세지 그대로 출력
			System.err.println(e.getMessage());
		} catch(DepositMinusMoneyException e) {
			e.printStackTrace();
			System.err.println(e.getMessage());
		}
		System.out.println("출금 후 잔액 : " + acc.getBalance() +"원");
	}
}

위와 같이 어떤 종류의 예외가 발생했는지 정확히 알 수 있기 때문에 유지보수성을 높일 수 있다.

'네트워크캠퍼스 > JAVA' 카테고리의 다른 글

자바 API  (0) 2024.01.18
자바의 예외처리전략  (0) 2024.01.18
인터페이스  (0) 2024.01.16
추상화  (0) 2024.01.15
final  (0) 2024.01.15

+ Recent posts