본문 바로가기
Java

Java_예외처리

by 유서담 2023. 11. 22.

예외처리

 

  • 예외처리란 프로그램 실행 시 발생할 수 있는 오류에 대비하는 것으로 프로그램 비정상종료를 막고 실행 상태를 유지하는 것

 

오류의 종류

 

에러(Error)

  • 시스템, 운영체제, JVM의 잘못으로 발생되는 것
  • 개발자가 해결할 수 있는 문제가 아님
  • 예외처리의 대상이 아님

 

예외(Exception)

  • 예외는 개발자의 코딩실수나 사용자의 잘못된 프로그램 사용으로 발생하는 오류
  • 예외는 예외처리를 통해서 비정상적인 종료를 예방할 수 있다
  • 예외는 UncheckedException과 CheckedException으로 구분

 

UncheckedException

  • RunTimeException 클래스와 그 자식 클래스들이다
  • 주로 개발자의 코딩 실수로 발생되는 오류들
  • 컴파일러가 예외처리 여부를 체크하지 않는다

 

UncheckedException 주요 클래스

 

클래스명 클래스 설명
RunTimeException 모든 Unchecked Exception의 부모 클래스
NullPointException 참조변수의 값이 Null인 상태에서 필드나 메소드를 사용할 때 발생하는 예외 클래스
ClassCastException 클래스 형변환이 가능하지 않을 때 발생하는 예외 클래스
ArithmeticException 나눗셈에서 어떤 값을 0으로 나눌 때 발생하는 예외 클래스
IndexOutOfBoundsException 배열, 리스트, 문자열에서 인덱스 범위를 벗어난 위치를 조회했을 때 발생하는 예외 클래스
NumberFormatException 숫자가 아닌 문자를 포함하고 있는 문자열을 정수나 실수로 변환할 때 발생하는 예외 클래스
Integer.parseInt(s), Double.parseDouble(s)등을 실행할 때 발생

 

 

CheckedException

  • Exception 클래스와 Exception 하위 클래스 중에서 RuntimeException 클래스의 하위 클래스가 아닌 예외 클래스
  • 사용자의 잘못된 사용으로 인해 발생하는 오류들
  • 컴파일러가 예외처리 구현 여부를 반드시 체크
    • 예외처리 관련 코드가 구현되어 있지 않으면 컴파일 오류가 발생
    • 최신의 라이브러리나 프레임워크에서는 CheckedException의 사용비중이 점점 줄어들고 있다

 

CheckedException의 주요 클래스

 

클래스명 클래스 설명
Exception 모든 Checked Exception 의 부모 클래스
ClassNotFoundException 클래스파일을 찾을 수 없을 때 발생하는 예외클래스
IOException 읽기/쓰기 하는 도중 오류가 발생했을 때 발생하는 예외클래스
네트워크를 통해서 다른 컴퓨터와 데이터 교환중 오류가 발생했을 때 발생하는 예외클래스
FileNotFoundException 파일을 찾을 수 없을 때 발생하는 예외클래스
SQLException 데이터베이스 엑세스 작업 중 오류가 발생했을 때 발생하는 예외클래스

 

 

Exception의 계층도

 

 

 

예외처리하기

  • try ~ catch 구문을 사용해서 직접 예외를 처리할 수 있다
  • throws를 사용해서 예외처리를 위임할 수 있다

 

try ~ catch 구문으로 예외처리하기

 

try {
	예외를 발생시키는 수행문;
	예외를 발생시키는 수행문;
    
} catch(자식예외클래스명 변수명) {
  	예외 발생시 실행할 수행문
    
}  catch(부모예외클래스명 변수명) {
  	예외 발생시 실행할 수행문
    
}  catch(Exception 변수명) {
  	예외 발생시 실행할 수행문
}

 

  • catch 블록을 여러 개 작성할 때는 부모타입예외를 잡은 catch 블록을 아래쪽에 위치시켜야 한다
  • 맨 아래쪽 catch 블록에 Exception 타입을 예외를 잡을 수 있도록 구성하면 예상하지 못한 예외도 잡을 수 있다
  • 발생이 예상되는 예외들이 부모-자식 관계가 아닐때는 순서와 상관없이 catch 블록을 구성해도 된다
  • catch 블록에서 선언한 변수는 해당 블록에서만 사용할 수 있다 

 

FileNotFoundException 예제

 

try {
	Properties prop = new Properties();
	prop.load(new FileReader("config.properties"));
} catch (FileNotFoundException ex) {
	System.out.println(ex);
	System.out.println("파일을 찾을 수 없습니다");
} catch (IOException ex) {
	System.out.println(ex);
	System.out.println("파일 읽기 작업 중 오류가 발생하였습니다");
}

 

  • FileNotFoundException : 지정된 경로에 해당하는 파일을 찾을 수 없을 때 던져지는 예외다
  • I/OException : 입출력 작업 중 오류가 발생했을 때 던져지는 예외

 

 

throws로 예외처리 위임하기

 

  • 메소드에서 발생하는 예외를 직접 처리하지 않고, 그 메소드를 호출하는 측에서 예외처리를 위임하는 것
  • throws 키워드를 사용해서 예외처리를 위임할 수 있다

 

throws 구문으로 예외처리하기

 

public static void main(String[] args) {
	try {
		first();
	} catch (IOException ex) {
		System.out.println(ex);
		System.out.println("입출력 작업 중 오류가 발생하였습니다");
	}
}
	
public static void first() throws IOException{
	second();
}
	
public static void second() throws IOException{
		
	FileWriter writer = new FileWriter("app3.txt");
	writer.write("예외처리 위임하기");
	writer.close();
}

 

  • 해당 메소드에서 예외처리와 관련한 코드의 작성을 할 필요가 없다.
  • 예외처리를 각각의 메소드에서 직접 개별적으로 처리하지 않고, 한 군데에서 일괄처리하게 만들 수 있다.

 

예외일괄처리 사진

 

 

예외일괄처리 예시

 

public class App1 {
	
	/**
	 * 고객등급과 주문금액을 전달받아서 적립포인트를 반환한다
	 * @param grade 고객등급
	 * @param orderPrice 주문금액
	 * @return 적립포인트
	 */
	public static int getPoint(String grade, int orderPrice) {
		Map<String, Double> map = new HashMap<String, Double>();
		map.put("gold", 0.05);
		map.put("silver", 0.03);
		map.put("bronze", 0.01);
		
		if(!map.containsKey(grade)) {
			throw new RuntimeException("[" + grade +"] 가 없거나 유효한 등급이 아닙니다");
		}
		
		
		// 고객등급에 해당하는 적립률 조회하기
		double rate = map.get(grade);
		
		// 적립률과 주문금액을 이용해서 적립포인트 계산하기
		int point = (int)(orderPrice*rate);
		
		// 적립포인트 반환하기
		return point;
	}
	
	public static void main(String[] args) {
		
		try {
			int point1 = getPoint("gold", 10000000);
			int point2 = getPoint("silver", 10000000);
			int point3 = getPoint("", 10000000);
			
		} catch (RuntimeException ex) {
			// 예외일괄처리하는 곳에서는 실질적인 예외처리 코드를 작성한다.
			// 1. 예외발생내용을 로그에 기록하기
			// 2. 오류 메세지를 경고창등을 통해서 사용자에게 알려주기
			// 3. 오류를 수정할 수 있도록 수행문 실행내용 출력하기
			// + 1번, 3번은 프로그램을 유지보수하는 개발자를 위한 처리
			// + 2번은 이 프로그램을 사용하는 사용자를 위한 처리
			
			ex.printStackTrace();
			
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}

 

public class App2 {

	public static int getPoint(String grade, int orderPrice) {
		Map<String, Double> map = new HashMap<String, Double>();
		map.put("gold", 0.05);
		map.put("silver", 0.03);
		map.put("bronze", 0.01);
		
		if(!map.containsKey(grade)) {
			throw new InvalidCustomerGradeException(grade);
		}
		
		double rate = map.get(grade);
		int point = (int)(orderPrice*rate);
		
		return point;
	}
	
	
	public static void main(String[] args) {
		
		// 예외 일괄처리
		try {
			
		int point1 = getPoint("gold", 10000000);
		System.out.println("포인트 : " + point1);
		
		int point2 = getPoint("silver", 5000000);
		System.out.println("포인트 : " + point2);
		
		int point3 = getPoint("guest", 300000);
		System.out.println("포인트 : " + point3);
		
		} catch (InvalidCustomerGradeException e) {
			e.printStackTrace();
		}
	}

 

 

위에서 사용된 InvalidCustomerGradeException

public class InvalidCustomerGradeException extends RuntimeException{

	public InvalidCustomerGradeException () {}
	
	public InvalidCustomerGradeException (String message) {
		// super(message)는 RuntimeException의 생성자 메소드다
		super(message);
	}
}

 

 

 

예외클래스의 주요 API

 

 

Throwble

 

  • Error, Exception의 부모 클래스
  • 오류와 관련된 모든 메소드들이 구현되어 있다.
  • 모든 Error, Exception 클래스는 Throwable에 구현된 메소드를 상속받고, 사용할 수 있다.
  • 특별한 경우가 아니면 사용자정의 예외클래스를 정의할 때 생성자만 정의해도 된다.

 

Throwble의 주요생성자

 

  • public Throwable() { ... } : 기본생성자
  • public Throwable(String message) { ... } : 오류와 관련된 메세지를 전달받는 생성자
  • public Throwable(String message, Throwable cause) { ... } : 오류와 관련된 메세지 및 오류의 원인이 되었던 이전 예외객체를 전달받는 생성자
  • public Throwable(Throwable cause) { ... } : 오류의 원인이 되었던 이전 예외객체를 전달받는 생성자

 

Throwble의 주요메소드

 

  • Throwable getCouse() : 오류발생의 원인이 되었던 오류를 반환한다.
  • String getMessage() : 오류와 관련된 상세한 메세지를 반환한다.
  • void printStackTrace() : 오류 발생과 관련되어서 실행되었던 코드를 화면에 출력 및 오류검출을 위한 디버깅 작업에서 참조한다.
  • void printStackTrace(PrintStream s) : 오류 발생과 관련되어서 실행되었던 코드를 화면이 아닌 다른 곳에 출력하게 할 수 있다.
  • void printStackTrace(PrintWriter s) : 오류 발생과 관련되어서 실행되었던 코드를 화면이 아닌 다른 곳에 출력하게 할 수 있다.

 

 

String getMessage( ) 와 void printStackTrace( ) 예시

 

  • String getMessage( ) : 오류 메시지를 반환
  • void printStackTrace( ) : 오류 검출을 위한 프로그램 실행 정보를 화면에 출력
public static void main(String[] args) {
	try {
		Properties prop = new Properties();
		prop.load(new FileReader("config.properties"));
	} catch (FileNotFoundException ex) {
		// getMessage()는 오류메시지를 반환한다
		String errorMessage = ex.getMessage();
		System.out.println("오류메세지: "+errorMessage);
			
	// printStackTrace()는
	// 오류 검출에 필요한 오류 정보(예외클래스명 + 오류메시지)와
	// 프로그램 실행정보를 화면에 출력한다
		ex.printStackTrace();
	} catch (IOException ex) {
		ex.printStackTrace();
	}
}

 

 

ArithmeticException 예시

 

  • 어떤 숫자를 0으로 나눌때 발생하는 예외다
  • UncheckedException이다. (컴파일러가 예외처리 여부를 체크하지 않는다)
  • 나눗셈 연산은 ArithmeticException 예외가 발생할 가능성이 있는 수행문이다
  •  ArithmeticException 예외가 UncheckedException 이기 때문에 나눗셈 연산식에 대해서 예외처리 코드를 작성할 필요가 없다 

ArithmeticException의 예시 및 출력결과 값

 

 

NumberFormatException 예시

 

  • 숫자가 아닌 문자를 포함하고 있는 텍스트를 숫자로 변환할 때 발생되는 예외다
  • NumberFormatException은 UncheckedException이다.
public static void main(String[] args) {
	String str ="1sdfasdfsd";
	int pageNo = 1;
	try {
		pageNo = Integer.parseInt(str);
	} catch (NumberFormatException ex) {

	}
	System.out.println("요청한 페이지 번호 : "+pageNo);
		
	int start = (pageNo -1)*10 +1;
	int end = pageNo*10;
		
	System.out.println("조회시작번호 : "+ start);
	System.out.println("조회종료번호 : "+ end);
}

 

public static void main(String[] args) {
// 페이지 번호
	String str = "2 ";
	int pageNo = NumberUtils.toInt(str, 1);
	int start = (pageNo -1)*10 +1;
	int end = pageNo * 10;
		
	System.out.println("페이지 번호 : "+ pageNo);
	System.out.println("조회시작번호 : " + start);
	System.out.println("조회종료번호 : " + end);
}

 

 

위에서 사용한 코드문에서 사용한 클래스 메소드

 

finally 블록

 

  • finally 블록에는 예외 발생 유무와 상관없이 반드시 실행되어야 하는 수행문을 작성하는 블록이다.
  • 애플리케이션 실행 중 점유했던 시스템의 자원(리소스)을 해제하는 코드(수행문)을 주로 작성한다.
  • 데이터베이스 엑세스 작업에서 트랜잭션처리와 관련된 코드를 작성한다.

 

finally 블록 예시

 

try {
	1 - 오류 발생이 예상되는 수행문;
    
	2 - 1번 위의 수행문이 정상적으로 수행되었을 때 실행되는 수행문;
	2 - 1번 위의 수행문이 정상적으로 수행되었을 때 실행되는 수행문;
	2 - 1번 위의 수행문이 정상적으로 수행되었을 때 실행되는 수행문;
} catch (예외클래스 변수명) {
	3 - 1번 수행문에서 오류가 발생했을 때, 실행되는 수행문 
} finally {
	4 - 1번 수행문의 정상수행/오류발생과 상관없이 반드시 실행되는 수행문;
}

 

  • 1번과 2번 수행문은 시스템의 자원을 사용하는 수행문이다.
  • 4번 수행문은 1번/2번 수행문에서 사용했던 시스템의 자원(I/O자원, 네트워크 자원, 데이터베이스 연결자원 등)을 반납하는 수행문
  • java 7부터 프로그램이 사용했던 자원을 자동으로 반납하는 기능이 추가되면서 자원을 반납하기 위한 finally 블록은 필요가 없어졌다
  • 일반적으로 데이터베이스 엑세스 작업에는 외부 라이브러리(mybatis, jpa)를 사용하게 되는데, 이런 라이브러리들은 데이터베이스 작업과 관련된 자원 반납을 자동으로 처리

 

public static void main(String[] args) {
	FileWriter writer = null;
		try {
			writer = new FileWriter("app7.txt");
			writer.write("finally 연습");
			
		} catch (IOException ex) {
			
		} finally {
			try {
				if(writer != null) {
					// FileWriter 객체가 사용했던 
					// 시스템의 입출력 직원을 반납하는 수행문이다.
				writer.close();									
			}
		} catch (IOException e) {
			// 이 catch 블록에는 수행문을 작성하지 않는다
		}
	}
}

 

 

finally 블록과 조금 다른 try ~ catch ~ resource 블록

 

  • try 블록에 자원객체를 전달하면, try 블록이 끝나면 자동으로 자원을 반납하는 기능이 지원되는 블록이다
try (SomeResource resource = new SomeResource()) {
	resource.method();
	resource.method();
} catch ( ) {

}

 

try (SomeResource1 resource1 = new SomeResource1();
	SomeResource2 resource2 = new SomeResource2() ) {
	resource.method();
	resource.method();		
} catch () {

}

 

  • AutoCloseable 인터페이스를 구현한 클래스는 try ~ catch ~ resource 블록을 사용하면 사용했던 자원을 자동으로 반납처리한다

 

예외의 변환

  • 실제 발생한 예외 대신 사용자정의 예외를 발생시키는 것
  • throw 키워드를 사용
  • throw new 예외클래스(오류메세지, 실제발생한예외)
  • 여러 종류의 예외를 처리하는 것보다 사용자정의 예외만 처리하도록 하는 것이 목적

예외처리 변환 작성예시

 

try {
    예외발생이 예상되는 코드 // SQLException 이 발생예상됨
  } catch (SQLException e) {
    throw new 사용자정의예외클래스("메세지", e);
  }

 

 

예외처리 변환예시 

 

public class App1 {

	// 이 메소드에서 발생하는 FileNotFoundException, IOException을
	// try ~ catch 구문을 이용해서 잡고, 대신 HtaException을 발생시켰다
	public static String readfile() {
		try (BufferedReader reader = new BufferedReader(new FileReader("sample.txt"))){
			String text = reader.readLine();	
			
			return text;
		} catch (FileNotFoundException e) {
			throw new HtaException("sample.txt",e);
		} catch (IOException e) {
			throw new HtaException(e);
		}
	}
	
	// 이 메소드에서 발생한 ParseException을 try ~ catch 구문을 이용해서 잡고
	// 대신 HtaException을 발생시켰다.
	public static Date toDate(String text) {
		try {
			SimpleDateFormat simple = new SimpleDateFormat("yyyy-MM-dd");
			Date date = simple.parse(text);
			return date;
		} catch (ParseException e) {
			throw new HtaException(text,e);
		}
		
	}
	
	public static void main(String[] args) {
		try {
			String str = readfile();
			System.out.println("읽어온 텍스트 : "+ str);
			
			toDate("dfasfd");
		} catch (HtaException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

 

 

위에서 사용된 HtaException

public class HtaException extends RuntimeException{

	public HtaException(String message) {
		super(message);
	}
	
	public HtaException (Throwable cause) {
		super(cause);
	}
	
	public HtaException (String message, Throwable cause) {
		super(message,cause);
	}
}

 

 

 

강제 예외발생

  • 업무로직상의 오류가 있는 경우(비밀번호 불일치, 잔액부족, 로그인 실패 등) 예외를 강제로 발생시켜서 해당 기능을 호출한 측에게 오류가 발생했음을 알리는 것이다.
  • throw 키워드를 사용

강제 예외발생 작성예시

 

  public void login(String id, String password) {
    User savedUser = userService.getUser(id);
    if (savedUser == null) {
      throw new UserNotFoundException("아이디가 존재하지 않습니다.");
    }
    if (!savedUser.getPasswrd.equals(password)) {
      throw new NotMatchedPasswordException("비밀번호가 일치하지 않습니다.");
    }
    ...
  }

'Java' 카테고리의 다른 글

Java_I/O(입출력)  (1) 2023.11.27
Java_객체의 직렬화와 역직렬화  (1) 2023.11.26
Java_Map, Comparable  (2) 2023.11.21
Java_Collection Framework, Iterator  (0) 2023.11.21
Java_제네릭(Generic)  (0) 2023.11.17