Exception(예외처리) - 3

목차

    Exception(예외처리)

    💡 학습 목표
        1. 예외 처리에 대해서 알아 보자.
        2. 프로그램에서의 오류와 예외 처리 방법
        3. 상속을 활용한 사용자 정의 예외 클래스 만들기

    1. 예외 처리에 대해서 알아 보자.

    • 자바 프로그래밍에서의 예외 처리(Exception Handling)는 프로그램 실행 중 발생할 수 있는 예상치 못한 상황(예외)을 관리하는 방법이다. 예외 처리를 통해 프로그램의 비정상적인 종료를 막고, 예외 상황을 보다 적절하게 처리하여 프로그램의 안정성과 신뢰성을 높일 수 있다.
    • 자바에서는 다음과 같은 방법으로 예외를 처리한다
    try {
        // 예외가 발생할 수 있는 코드
    } catch (ExceptionType1 e) {
        // ExceptionType1 예외를 처리하는 코드
    } catch (ExceptionType2 e) {
        // ExceptionType2 예외를 처리하는 코드
    }
    try {
        // 예외가 발생할 수 있는 코드
    } catch (Exception e) {
        // 예외 처리 코드
    } finally {
        // 항상 실행되는 코드
    }

    finally 블록: try 블록의 실행 여부와 관계없이 항상 실행되어야 하는 코드(예: 자원 해제 로직)를 포함.

    finally 블록은 모든 catch 블록 다음에 온다.

    if (someCondition) {
        throw new Exception("Custom Error Message");
    }
    
    ----------------------------------------------------------------
    
    public void someMethod() throws IOException, NullPointerException {
        // 예외가 발생할 수 있는 코드
    }

    2. 프로그램에서의 오류와 예외처리 방법

    컴파일 오류(compile error)

    • 프로그램 코드 작성 중 발생하는 문법적 오류
    • 최근에는 개발 환경(eclipse)에서 대부분의 컴파일 오류는 detection 됨

    실행 오류(runtime error)

    • 실행 중인 프로그램이 의도 하지 않은 동작(bug)을 하거나 프로그램이 중지 되는 오류
    • 실행 오류는 비정상 종료가 되는 경우 시스템의 심각한 장애를 일으킬 수 있다.

    예외 처리의 중요성

    • 프로그램의 비정상 종료를 피하여 시스템이 원할이 실행되도록 함
    • 실행 오류가 발생한 경우 오류의 과정을 재현하는 것은 현실적으로 힘들다
    • 오류가 발생한 경우 log를 남겨서 추후 log 분석을 통해 그 원인을 파악하여 bug를 수정하는 것이 중요

    오류와 예외 클래스

    • 시스템 오류(error) : 가상 머신에서 발생, 프로그래머가 처리 할 수 없는 오류
      동적 메모리가 없는 경우, 스택 메모리 오버플로우등
    • 예외(Exception) :프로그램에서 제어 할 수 있는 오류
      읽어들이려는 파일이 존재하지 않거나, 네트웍이나 DB연결이 안되는 경우등
    • 자바는 안전성이 중요한 언어로 대부분 프로그램에서 발생하는 오류에 대해 문법적으로 예외 처리를 해야함

    모든 예외 클래스의 최상위 클래스는 Exception 클래스

    시나리오 코드 1 (try-catch 문)

    package useful.ch03;
    
    public class ArrayExceptionHandling {
    
    	// 메인 쓰레드
    	public static void main(String[] args) {
    
    		// 런타임 에러
    		int[] arr = { 1, 2, 3, 4, 5 };
    
    		try {
    			// 예외가 발생할 수 있는 코드를 넣어서 수행 시킨다.
    			for (int i = 0; i < 10; i++) {
    				System.out.println(arr[i]);
    			}
    		} catch (ArrayIndexOutOfBoundsException e) {
    			System.out.println("인덱스가 배열의 크기를 벗어 났습니다.");
    			// System.out.println(e.getMessage());
    		}
    		System.out.println("비정상 종료 되지 않았어요!");
    
    		// ArrayIndexOutOfBoundsException t1;
    
    	} // end of main
    
    } // end of class

    시나리오 코드 2 (try-catch-finally 문)

    package useful.ch03;
    
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.IOException;
    
    public class FileExceptionHandling {
    
    	// 메인 쓰레드
    	public static void main(String[] args) {
    
    		FileInputStream fis = null;
    		try {
    			fis = new FileInputStream("demo.txt");
    			// return;
    		} catch (FileNotFoundException e) {
    			System.out.println("catch 구문 실행!!!");
    			e.printStackTrace();
    		} finally {
    			// 반드시 수행 되어야 하는 코드 영역
    			// 심지어 return 키워드를 만나더라도 여기는 수행된다.
    			System.out.println("finally 블록 수행");
    			try {
    				fis.close(); // 닫는 시점에 fis. <-- 객체가 생성 안될 경우도 존재함.
    			} catch (IOException e) {
    				e.printStackTrace();
    			} 
    		}
    		System.out.println("비정상 종료 되지 않았어요~");
    
    	} // end of main
    
    } // end of class

    printStackTrace() 메소드

    package useful.ch03;
    
    import java.util.Scanner;
    
    public class FinallyHandling {
    
    	public static void main(String[] args) {
    
    		// try-catch-finally
    		// 언제 finally를 사용해야 해?
    		// 자원을 반드시 닫아 주어야 할 때 등등...
    		Scanner sc = new Scanner(System.in);
    		try {
    			int result = sc.nextInt();
    			System.out.println("result : " + result);
    			// scanner 의 자원을 다 사용 했다면 자원을 해제해야 된다.
    		} finally {
    			sc.close();
    			System.out.println("자원 해제 완료");
    		}
    
    	}
    
    }

    예외가 발생 되더라도 finally 블록을 실행해서 자원을 닫을 수 있다.

    시나리오 코드 3 -  throws 예외 처리 던지기(미루기)

    package useful.ch03;
    
    public class ThrowsHandling {
    
    	// 메인 쓰레드
    	public static void main(String[] args) {
    
    		Calc calc = new Calc();
    		try {
    			// 던져서 강제성이 발생이 되고
    			// 사용하는 사람이 직접 예외 처리 핸들링을 할 수 있다.
    			calc.divide(10, 0);
    		} catch (ArithmeticException e) {
    			System.out.println("어떤 수를 0으로 나눌 수 없어요");
    		// Exception을 제일 마지막으로 (점점 범위가 넓어지도록 예외 처리)
    		} catch (Exception e) {
    			System.out.println(e.getMessage());
    		}
    		
    	} // end of main
    
    } // end of class
    
    class Calc {
    
    	public int divide(int n1, int n2) throws Exception {
    		// 사용자가 0을 입력하면 예외가 발생 될 수 있는 코드 영역
    		// 해결 방법
    		// 1. 해당 메서드에서 직접 예외 처리를 구현한다.
    		// 2. 사용하는 사람이 직접 예외처리를 할 수 있도록 던져 버린다.
    		return n1 / n2;
    	}
    
    }

    3. 상속을 활용한 사용자 정의 예외 클래스 만들기

    1. 클래스 설계 : 상속을 받아야 한다.
    2. 예외 클래스를 활용하는 클래스에서 throws와 throw 키워드 사용
    3. 코드 실행 시점에서 테스트 및 예외 처리 작성

    시나리오 코드 4 ( 사용자 정의 예외 클래스)

    package useful.ch03;
    
    /**
     * 사용자 정의 예외 클래스 생성
     */
    public class PasswordException extends RuntimeException{
    	
    	// getMessage를 쓰기 위해서 멤버 변수 message 초기화
    	public PasswordException(String message) {
    		super(message);
    	}
    }
    package useful.ch03;
    
    public class Password {
    
    	private String pwd;
    
    	// getter
    	public String getPwd() {
    		return pwd;
    	}
    
    	// setter
    	public void setPwd(String pwd) throws PasswordException {
    		if (pwd == null) {
    			// System.out.println("비밀번호는 null 값일 수 없습니다.");
    			throw new PasswordException("비밀번호는 null 값일 수 없습니다.");
    		}
    
    		if (pwd.length() < 5) {
    			// System.out.println("비밀번호는 5자 이상이어야 합니다.");
    			throw new PasswordException("비밀번호는 5자 이상이어야 합니다.");
    		}
    
    		// 정규 표현식을 활용할 수 있다.
    		// pwd <== a~z, A~Z 안의 값만 있으면 true
    		// pwd <== a, 10, ! ==> false
    		if (pwd.matches("[a-zA-Z]+")) {
    			// System.out.println("비밀번호는 숫자나 특수문자를 포함해야 합니다.");
    			throw new PasswordException("비밀번호는 숫자나 특수문자를 포함해야 합니다.");
    		}
    		this.pwd = pwd;
    	}
    }

    * 정규 표현식

      [...] : 대괄호는 문자열을 나타냄

      a-z, A-Z 모든 알파벳 문자열을 말한다.

      + : 바로 앞의 표현식이 하나 이상은 반복되어야 한다.

      즉, "Hello","World" --> true 반환 "A1" --> false

     

    package useful.ch03;
    
    public class PasswordMainTest {
    
    	public static void main(String[] args) {
    
    		Password password = new Password();
    
    		String inputPwd = null;
    		inputPwd = "ab";
    
    		// 테스트 해보니까 아래 코드는 실행 시점에 예외가 발생되는 코드이다
    		// 해결
    		// 1. 직접 예외 처리
    		// 2. 던지기
    		try {
    			password.setPwd(inputPwd);
    
    		} catch (PasswordException e) {
    			System.out.println("우리가 정의한 password 예외가 발생");
    			System.out.println(e.getMessage());
    		} catch (Exception e) {
    			System.out.println("예외 발생 : " + e.getMessage());
    		}
    
    	} // end of main
    
    } // end of class
    정규식(Regular Expression, 줄여서 regex 또는 regexp)은 문자열을 처리할 때 사용되는 강력한 도구로, 복잡한 검색, 매칭, 치환 작업을 간단하고 유연하게 수행할 수 있도록 돕습니다. 특정한 규칙을 가진 문자열의 집합을 표현하는데 사용되며, 이 규칙에 따라 문자열의 검색, 분할, 대체, 검증 등 다양한 작업을 자동화할 수 있습니다.

    연습 문제

    NickName 클래스 설계
    String nick;
    get
    set : 예외를 던지는 코드 추가
    NickNameException : RuntimeException 상속
    NickNameTest 클래스로 확인하기
    package useful.ch03;
    
    public class NickNameException extends RuntimeException{
    	public NickNameException(String message) {
    		super(message);
    	}
    }
    package useful.ch03;
    
    public class NickName {
    	private String nick;
    	
    	// getter
    	public String getNick() {
    		return nick;
    	}
    	// setter
    	public void setNick(String nick) throws NickNameException{
    		if (nick == null) {
    			throw new NickNameException("닉네임은 null 값일 수 없습니다.");
    		}
    		if (nick.length() < 2) {
    			throw new NickNameException("닉네임은 두 글자 이상이어야 합니다.");
    		}
    		if (nick.length() > 10) {
    			throw new NickNameException("닉네임은 열 글자 이하여야 합니다.");
    		}
    		if (!nick.matches("[0-9a-zA-Z가-힣_]+")) {
    			throw new NickNameException("닉네임은 _를 제외한 특수문자를 포함할 수 없습니다.");
    		}
    		this.nick = nick;
    	}
    }
    package useful.ch03;
    
    public class NickNameTest {
    
    	public static void main(String[] args) {
    		NickName nickName = new NickName();
    		String inputNick = null;
    		inputNick = "12345678900";
    		try {
    			nickName.setNick(inputNick);
    		} catch (NickNameException e) {
    			System.out.println("사용자 정의 예외 발생 : " + e.getMessage());
    		} catch (Exception e) {
    			System.out.println("예외 발생 : " + e.getMessage());
    		}
    		System.out.println(nickName.getNick());
    	}
    
    }

    Java 유용한 클래스 - 3 으로 돌아가기