자바프로그래밍 예외처리

최주영·2023년 3월 22일
0

자바

목록 보기
14/30

✅ 프로그램 오류

  • 종류 : 컴파일에러, 런타임에러, 시스템에러
  • 컴파일 에러 : 빨간줄 뜨는것! (개발자의 오타나 로직실수로) 실행하지못하는것

  • 런타임 에러 : 프로그램이 정상적으로 실행되지만 실행도중에 중간에 에러나는거 EX) 널포인터오류, 배열크기벗어난오류

  • 시스템 에러 : 램의 공간이 부족하거나, 전원이 차단되는 에러 (개발자가 처리할 수 없음)

이러한 에러를 해결하기 위해서는 소스 수정으로 해결 가능한 에러를 예외 라고 한다


✅ 예외 클래스 계층 구조

부모 계층의 클래스들이 자식 계층의 클래스들의 오류를 다 처리해준다
ex) catch문에 RuntimeException 넣으면 ArithmeticExcption, NullPointerException 등 자식 exception들을 다 처리할 수 있음


✅ Checked Exception

  • 예외 클래스를 만드려면 예외를 상속받는다 -> Exception
  • 새로운 예외를 발생시킬 때 throw 키워드 사용 -> 예외도 객체이므로 new로 생성 후 예외 발생
  • 예외를 메서드 밖으로 던질 때 throws 키워드 사용
public class MyCheckedException extends Exception {
   public MyCheckedException(String message) {
     super(message);
   }
}
public class Client { 
    public void call() throws MyCheckedException { // 해결못해서 밖으로 던지기 
    	throw new MyCheckedException("ex");	 // 예외 발생시키기 (ex = 메시지입력)
    }
}
public class Service {
	Client client = new Client(); 
    
    // 예외를 잡아서 처리하는 코드
    public void callCatch(){
    	try{
        	client.call(); // call 메소드에서 예외발생시 catch에서 예외처리
        }catch(MyCheckedException e){
        	// 예외처리로직
        }
    }
    
    // 체크 예외를 밖으로 던지는 코드
    public void catchThrow() throws MyCheckedException { // 예외를 잡지 않고 밖으로 던지기
    	client.call();
    }
    
}
//ex) 밑에 예외들은 무조건 try ~ catch로 예외처리 해야함!
// try ~ catch로 하지 않을시 프로그램 시작 자체가 안됨!
throw new FileNotFoundException();   //오류
throw new EOFException(); // 오류
throw new SQLException(); // 오류

예외처리 2가지 규칙

  • 예외는 잡아서 처리하거나 밖으로 던져야함
    -> 처리할 경우 -> try catch
    -> 예외를 밖으로 던질경우 (폭탄돌리기) -> 메소드 뒤에 throws 예외이름

  • 예외를 잡거나 던질 때, 지정한 예외뿐 아니라 그 예외의 자식들도 함께 처리할 수 있다
    -> Exception 을 지정하면, 자식 예외들 모두 처리 가능

Checked Exception 장단점

  • 장점 : 컴파일러를 통해 개발자가 실수로 예외를 누락하지 않을 수 있음 (Build전에 표시됨)
  • 단점 : 모든 체크예외를 잡거나 던져야하기 때문에, 불필요한 예외들도 모두 처리해야함

✅ Unchecked Exception

  • 예외를 잡거나, 던지지 않아도됨 -> 예외를 잡지않으면 자동으로 밖으로 던짐
    -> Checked Exception과 다르게 컴파일러가 이런 부분을 체크하지 않음

		// 발생하는 예외에 대해서 처리하지 않아도 되는 예외 (Unchecked Exception)
		 ArithmeticException : 수학적으로 계산이 불가능할 때 발생하는 Exception
		 int su = 10;
		 int su2 = 0;
		 System.out.println(su/su2);

		// ArrayIndexOutOfBounsException
		 배열의 인덱스범위를 초과해서 접근할 때 발생하는 예외
		 +int[] intArr = new int[5];
		 System.out.println(intArr[5]);

		// ClassCastException
		 클래스형변환을 잘못했을 때
		 Object o = new String("안녕");
		 Integer num = (Integer)o; // 문자열을 정수형으로 바꿀 수 없음

		// NullPointException
		 참조형변수에 null값이 있을 때 접근연산자를 사용하면 발생하는 예외
		 String name = null;
		 name.length(); // name에는 널값이 있는데 .연산자로 접근하고 있어서 발생

		// NumberFormatException
		 문자열을 숫자형으로 변환할 때 변환 불가능한 문자가 있는 경우
		 String name = null;
		 su = Integer.parseInt(name); //문자열을 정수로 바꿀 수 없음

		// InputMissMatchException
		 입력값의 타입이 일치하지 않을 때 발생
		 Scanner sc = new Scanner(System.in);
		 int su3 = sc.nextInt();

UnChecked Exception 장단점

  • 장점 : 신경쓰고 싶지 않는 언체크 예외를 무시할 수 있다
  • 단점 : 개발자가 실수로 예외를 누락할 수 있다

✅ 체크 예외 vs 언체크 예외

  • 체크예외 : 예외를 잡아서 처리하지 않으면 항상 명시적으로 throws 키워드로 예외를 던져야함
    -> Exception을 상속받은 예외는 체크 예외가 됨

  • 언체크예외 : 예외를 잡아서 처리하지 않아도 throws 키워드를 생략 가능
    -> RuntimeException을 상속받은 예외는 언체크 예외가 됨


✅ finally

try catch문을 이용해서 예외를 처리할 경우
정상적으로 연결이 되든 안되든 필수적으로 실행해야하는 기능이 있다 (ex : 자원 반환)
그럴 경우 catch 뒤 finally 키워드를 사용한다

try{
	// 정상흐름
}catch{
	// 예외흐름
}finally{
	// 반드시 호출해야하는 마무리 흐름
}

try 문이 시작되면, catch안에서 잡을 수 없는 예외가 발생해도 finally 코드는 어떤 경우라도 호출됨

try finally 만 사용할 수도 있음


✅ try-with-resources

try 에서 외부 자원을 사용하고, try 가 끝나면 외부 자원을 반납하는 패턴이 반복됨
-> 자바7에서부터 try-with-resources 라는 편의 기능을 제공함

이 기능을 사용하려면 먼저 AutoCloseable 인터페이스를 구현해야함
이 인터페이스를 구현하면 try가 끝나는 시점에 close() 가 자동으로 호출됨
-> try에서만 자원을 사용하기 때문에, catch문 시작전에 try 가 끝나는 순간 close() 호출됨


try-with-resources 장점

  • 모든 자원이 제대로 닫히도록 보장 (실수로 닫지 않는 것을 대비)
  • 명시적인 close() 호출이 필요 없으므로 코드가 간결해짐
  • 기존에는 try-> catch-> finallycatch 이후에 자원을 반납했지만
    try 블럭이 끝나면 즉시 close()를 호출한다 (더 빠른 자원해제가 가능)

✅ 예외 계층

  • 디테일한 예외들을 먼저 적어야함
try{
  // 정상흐름
}catch(ConnectException e){
  // 연결오류
}catch(NetworkClientException e){
	// 네트워크 오류
}catch(Exception e){
	// 알수없는 오류 -> 위 2개 예외로 처리못한 나머지 모든 예외들은 여기서 처리됨
}finally{
	// 무조건실행되는 로직
}

✅ 실무 예외처리

실무에서는 수많은 예외처리들이 있다

하지만 실제로 개발자가 catch 문을 사용해서 처리할 수 있는 것이 많지 않다
그래서 모든 예외들을 잡는 것은 좋지 않다 -> catch 로 처리해도 똑같은 문제가 발생하기 때문에
ex) 데이터베이스 서버 문제

그러면 모든 예외들을 throws를 사용해서 던지면 되지 않을까?
하지만 요즘 라이브러리도 많이 사용하면서, 수 많은 예외들이 존재한다
모든 예외들을 해결하지 않고 밖으로 던지면 모든 클래스에서 지저분한 코드만 추가해지는 것 뿐이다
-> 해결하지 못한 예외를 폭탄처럼 돌리기 때문에

그러면 최상위 계층인 Exception 을 던져서 처리하면 되지 않을까?
코드는 깔끔해지지만 치명적인 문제가 발생한다
-> 최상위 타입인 Exception을 던지게되면 다른 체크 예외를 체크할 수 있는 기능이 무효화됨
-> 중요한 체크 예외를 다 놓치게 됨
-> 중간에 중요한 체크 예외가 발생해도 컴파일러는 문법에 맞다 판단해서 컴파일 오류를 발생 X
즉 꼭 필요한 경우가 아니라면, Exception 자체를 밖으로 던지는 것은 좋지 않다


요즘은 본인이 해결할 수 있는 예외만 잡아서 처리하고
해결할 수 없는 예외는 던지는것이 더 나은 선택일 수도 있다

그리고 처리할 수 없는 예외들은 공통 으로 처리할 수 있는 곳을 만들어서 한곳에서 해결한다
ex) 처리할 수 없는 모든 예외들 -> 현재 시스템에 문제가 있습니다 오류 메시지 출력

// 공통 예외처리
private static void exceptionHandler(Exception e) {
     //공통 처리
     System.out.println("사용자 메시지: 죄송합니다. 알 수 없는 문제가 발생했습니다.");
     System.out.println("==개발자용 디버깅 메시지==");
     e.printStackTrace(System.out); // 스택 트레이스 출력
     //e.printStackTrace(); // System.err에 스택 트레이스 출력
     //필요하면 예외 별로 별도의 추가 처리 가능
     if (e instanceof SendExceptionV4 sendEx) {
       System.out.println("[전송 오류] 전송 데이터: " + sendEx.getSendData());
       }
 }
 
try { 
   networkService.sendMessage(input);
} catch (Exception e) { // 모든 예외를 잡아서 처리
   exceptionHandler(e);
}

최근 만들어진 라이브러리는 대부분 UnChecked Runtime 에러를 사용함


✅ 사용자 정의 예외 만들기

// CharCheckException 클래스
package com.bs.practice.charCheck.exception;

public class CharCheckException extends Exception{  // 최상위 예외클래스인 Exception 상속
	public CharCheckException() {
		// TODO Auto-generated constructor stub
	}
	
	public CharCheckException(String msg) {
		super(msg);  // 부모생성자로 메시지 호출
	}
}


// CharacterMenu 클래스
package com.bs.practice.charCheck.run;

import java.util.Scanner;

import com.bs.practice.charCheck.controller.CharacterController;
import com.bs.practice.charCheck.exception.CharCheckException;

public class CharacterMenu{
	
	public void menu() {
		Scanner sc = new Scanner(System.in);
		System.out.print("문자열 입력 : ");
		String str = sc.nextLine();
		int cnt = 0;
		try {
			cnt = new CharacterController().countAlpha(str); // 문자전달
		} catch (CharCheckException e) { // 예외발생시 경고문 출력
			e.printStackTrace();
		}
		

		if(cnt == 0) {
			return;
		}else {
			System.out.println("'"+str+"'"+"에 포함된 영문자 개수 : "+cnt);

		}
	}
}




// CharacterController 클래스
package com.bs.practice.charCheck.controller;
import com.bs.practice.charCheck.exception.CharCheckException;

public class CharacterController {
	
		public CharacterController() {}
		

		public int countAlpha(String s) throws CharCheckException { // 예외던지기
			int cnt =0;
			try{
				for(int i=0; i<s.length(); i++) {
					if((s.charAt(i) >= 'A' && s.charAt(i) <= 'Z')
						|| (s.charAt(i) >= 'a' && s.charAt(i) <= 'z')) {
						cnt++;
					}
					
					if(s.charAt(i) == ' ') { // 문자열에 공백 있을시 강제로 예외발생
						throw new CharCheckException("체크할 문자열 안에 공백이 포함되어 있습니다.");
					}
				}
				
			}catch(CharCheckException e){ // 예외발생시
				e.printStackTrace(); // 해당 문구 출력
				return 0;
			}
//			finally {
//				return cnt;
//			}
			return cnt;	
		}
}




✅ try~catch~finally 문을 이용해서 직접예외를 처리하기

		int[] intArr = { 1, 2, 3, 4, 5 };

		try {
			int a = intArr[5];   // 예외발생하는 부분까지만 실행하고 catch로 넘어감!
			System.out.println(a);
		} catch (ArrayIndexOutOfBoundsException e) { // 클래스라서 객체 e 만들어줌
			System.out.println("인덱스 부족해!"); // catch문에서 해결 방법 구현!
			int[] temp = new int[intArr.length 	 + 5];
			System.arraycopy(intArr, 0, temp, 0, intArr.length);
			intArr = temp;
			int a = intArr[5];
			System.out.println(a);
		}
		System.out.println("실행되니?");




		String name = null;
        
		try {
			if (name.length() < 2) { // 예외가 발생한 지점에서 바로 catch로 넘어감
				System.out.println("이름은 두글자 이상 작성해주세요");
			} else {
				System.out.println(name + " 참 멋진 이름이네요");
			}

		} catch (NullPointerException e) {
			System.out.println("name이 null 이면 처리 할 수 없습니다.");
		}
		System.out.println("예외처리 후 실행되는 로직");

	
    
    
    	// try~catch문을 작성했을 때 catch문을 여러개 작성할 수 있다.
		// 동시에 두개의 오류가 발생할 순 없다
		String[] names = {"유병승",null,"최하리"};
		try {
			for(int i=0; i<=names.length; i++) {
				if(names[i].length()>2) {
					System.out.println(name);
				}
			}
		}catch(NullPointerException e) { // 예외처리 1
			System.out.println("널포인트 Exception");
		}catch(ArrayIndexOutOfBoundsException e) { // 예외처리 2
			System.out.println("인덱스가 부족해");
		}
        
        
        
        // 위 코드와 같은 결과갖을 갖으며 |연사자를 통해서 둘중하나 걸렸을 때 처리할 수 있음
		String[] names = { "유병승", null, "최하리" };
		try {
			for (int i = 0; i <= names.length; i++) {
				if (names[i].length() > 2) {
					System.out.println(name);
				}
			}
		} catch (NullPointerException | ArrayIndexOutOfBoundsException e) {
			System.out.println(e.getMessage());
			e.printStackTrace();  // 오류구문이 빨간문장으로 뜨지만 프로그램은 종료되지 않음
			// printStackTrace는 어떤위치에서 어떤오류가 발생했는지 확인할 때 사용
			System.out.println("예외발생!!!");
		}




		// 부모 Exception은 자식 Exception이 발생했을 때 모두 처리 할 수 있다.
		// 즉 예외처리 최상위 클래스인 Throwable은 모든 예외를 처리 할 수 있음
		// 반대로 자식 예외로 부모 예외를 처리할 수 없음
		// 공통적으로 처리하는 부분이 동일하면 Exception으로 모든 예외를 처리하고
		// 오류부분을 일일이 찾아내고싶을 때는 해당 예외를 넣는다.
		// 주로 각각의 예외부분을 먼저 넣은 후에 마지막부분에 최상위 예외클래스를 넣는다.
		
		Object o = new String("test");
		try {
			int a =(int)o;
			a = intArr[11];
		}catch(Throwable e){
			System.out.println(e.getMessage());
			System.out.println("예외처리");
		}
		
		Object o = new String("test");
		try {
			int a =(int)o;
			a = intArr[11];
		}catch(Throwable e){
			System.out.println(e.getMessage());
			System.out.println("예외처리");
		}
        
        
        
        
        // 예외처리 구문에서 반드시 실행해야할 구문이 있는 경우
		// finally{예외가 발생하던, 안하던 무조건 실행되는 구문}
		// try,catch문에 break가 있어도 finally는 실행되고 넘어감

		try {
			String n = null;
			n.length(); // 널포인터 예외오류 발생
			System.out.println("try문");
		}catch(NullPointerException e) {
			System.out.println("catch문");
		
		}finally { // 예외가 발생하든 안하든 실행함
			System.out.println("반드시 실행해");
		}
        
        
        
        // 개발자가 원하는 순간에 Exception을 발생시킬 수 있음
		private void exceptionTest(Object o) {
			if(o instanceof String) {
				System.out.println(o);
			}else {
				// 직접예외를 발생시키기
				throw new IllegalArgumentException(); // 예외처리명은 마음대로 정해도됨
			}
		}
profile
우측 상단 햇님모양 클릭하셔서 무조건 야간모드로 봐주세요!!

0개의 댓글