수업내용/Java

[2022.10.11.화] 예외처리 활용

주니어주니 2022. 10. 11. 23:06

 

 

1-3. 예외처리 활용

 

1) 예외처리 위임하기

 

 

 

  • method1에서 발생이 예상되는 예외가 checkedException 일 때,  반드시
    - try catch를 사용해서 직접 예외처리를 하거나
    - throws를 사용해서 예외처리를 위임

  • method2에서 발생이 예상되는 예외가 uncheckedExciption 일 때,
    - 예외처리를 하지 않아도 된다.
    - try catch를 사용해서 직접 예외처리를 할 수 있다. 근데 안해도 됨 
    - 예외처리를 위임시킬 때는 throws를 사용하지 않아도 이 메소드에서 발생한 예외가 이 메소드를 호출한 측에게 예외가 전달되고, 그 호출한 메소드로 예외처리가 위임된다 (위임할 때는 throws를 안적어도 됨) 
    --> throws에 던질 메소드들을 안적어도 되니까 얘를 많이 씀

 

 

- checkedException과  uncheckedExciption 연습

import java.io.FileWriter;
import java.io.IOException;

public class ExceptionApp1 {

	public static void main(String[] args) throws IOException{
		
		// main() 메소드에서 method1와 method2를 호출했을 때 발생이 예상되는 예외를 다시 예외처리 위임할 수 있다.
		// main() 메소드에서 예외처리를 위임(throws)하면, 예외가 발생했을 때 자바가상머신으로 전달되고, 프로그램이 종료된다.
		// 따라서, main() 메소드에서 예외처리를 위임하는 것은 실제로 아무런 예외처리를 하지 않는 것과 같다. 
        	// 예외처리를 안해도 -> 예외가 자바가상머신으로 전달되는데, main에서 자바가상머신으로 throws -> 예외가 자바가상머신으로 전달
		ExceptionApp1.method1();
		ExceptionApp1.method2("1234", "5678");
	}
	
	// checkedException 발생이 예외되는 수행문을 실행하는 메소드
	// 컴파일러가 예외처리 여부를 검사하기 때문에 try ~ catch , throws 중 하나를 이용해서 예외처리 해야 함
	// method1() 메소드에서 발생이 예상되는 예외를 직접 처리하지 않고, 
	// 	이 메소드를 호출한 측에게 예외를 전달하고  
	//	이 메소드를 호출한 측에서 예외처리를 위임하기 위해서는 throws 키워드 사용
	// 	이 메소드를 호출한 측에서는 반드시 예외처리(try ~ catch로 직접 처리 or throws로 다시 위임)을 해야 함
	public static void method1() throws IOException {		
		FileWriter writer = new FileWriter("sample.txt");
		writer.write("연습");
		writer.flush();
		writer.close();
		
	}
	
	// uncheckedException 발생이 예외되는 수행문을 실행하는 메소드
	// 컴파일러가 예외처리 여부를 검사하지 않기 때문에 try ~ catch , throws를 작성할 필요 없음
	// method2() 메소드에서 예외가 발생하면, 
	// 	이 메소드를 호출한 측에게 예외가 전달되고 ( 작성하지 않아도 전달됨 ) 
	//	이 메소드를 호출한 측에서 예외처리를 해아 한다. (에외처리 위임) 
	// 	이 메소드를 호출한 측에서도 예외처리를 반드시 할 필요는 없음 
	public static void method2(String str1, String str2) {
		int number1 = Integer.parseInt(str1); 
		int number2 = Integer.parseInt(str2); 
		
		System.out.println("합계: " + (number1 + number2));
	}
}

 

 

 

 

 

2) 예외클래스의 주요 API

 

 

Throwable 클래스 

  • Error, Exception의 부모 클래스
  • 오류와 관련된 모든 메소드들이 구현되어 있다.
  • 모든 Error, Exception 클래스는 Throwable에 구현된 메소드를 상속받고, 사용할 수 있다.
  • 특별한 경우가 아니면 사용자정의 예외클래스를 정의할 때 생성자만 정의해도 된다.
  • 멤버변수
    • private String message
      오류 메세지를 저장하는 변수
    • private Throwable cause
      오류의 원인이 되었던 예외객체를 저장하는 변수
  • 주요 생성자
    • public Throwable() { ... }
      기본생성자
    • public Throwable(String message) { ... }
      오류와 관련된 메세지를 전달받는 생성자
    • public Throwable(String message, Throwable cause) { ... }
      오류와 관련된 메세지 및 오류의 원인이 되었던 이전 예외객체를 전달받는 생성자
    • public Throwable(Throwable cause) { ... }
      오류의 원인이 되었던 이전 예외객체를 전달받는 생성자
  • 주요 메소드
    • String getMessage()
      오류와 관련된 상세한 메세지를 반환한다.
    • void printStackTrace()
      디버깅에 필요한 오류정보를 출력한다.
      오류 발생과 관련되어서 실행되었던 코드를 화면에 출력한다.
      오류검출을 위한 디버깅 작업에서 참조한다.

 

 

Exception 예외 클래스에는 생성자만 있고 메소드가 없어

-> Throwable에 있는 메소드를 그대로 사용 (실질적인 멤버 메소드는 Throwable클래스만 갖고있음)

   (Throwable 클래스가 부모클래스이기 때문에, 이 안에 있는 메소드는 모든 예외 클래스가 사용할 수 있음) 

-> 오류 내용들을 담아야 되는데 변수도 없어

->  super를 통해 부모의 생성자를 호출해서 부모의 변수 message, cause에 오류 내용을 담음

 

-> 대부분의 예외클래스들은 생성자만 갖고있고, 이 생성자 안에는 부모클래스인 Throwable클래스의 생성자 메소드를 호출하는 super( ) ; 메소드가 있음

-> 사용자 정의 예외클래스를 만들 때도 생성자만 만들면 됨 

 

 

 

-예외객체의 주요 메소드 사용하기

public class ExceptionApp2 {

	public static void main(String[] args) {
		
		// 예외 객체의 주요 메소드 사용하기
		try {
			int number = Integer.parseInt("1234A");
			System.out.println(number);
		} catch(Exception ex) {
			// 오류 메세지 조회
			String errorMessage = ex.getMessage();	// getMessage : 모든 예외객체가 갖고있는 메소드 
			System.out.println("오류 메세지: " + errorMessage);
			
			// 디버깅에 필요한 정보 출력하기 (오류를 찾아가는 과정)
			System.out.println("## 디버깅에 필요한 정보 출력하기");
			ex.printStackTrace();	// printStackTrace : 오류과정을 알려주는 메소드
		}
	}
}

 

오류 메세지: For input string: "1234A"
## 디버깅에 필요한 정보 출력하기
java.lang.NumberFormatException: For input string: "1234A"
	at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67)
	at java.base/java.lang.Integer.parseInt(Integer.java:668)
	at java.base/java.lang.Integer.parseInt(Integer.java:786)
	at day26.ExceptionApp2.main(ExceptionApp2.java:9)

 

 

system. out -> 정상적인 출력 (흰색)

system. err -> 오류메세지 출력 (빨간색)

 

 

 

 

 

3) 예외 강제 발생

 

  • 업무로직상의 오류가 발생한 경우(비밀번호 불일치, 잔액부족, 로그인 실패 등), 예외객체를 생성하고 예외를 강제로 발생시켜서 이 메소드를 호출한 측에게 전달(던지기, 오류가 발생했음을 알리는 것)하는 것이다.
    ===> 이게 개발자가 해야하는 일 
    (비밀번호 불일치, 잔액부족 같은 오류클래스는 따로 없단말이야 -> 만들어줌) 
  • 오류 상황에 대한 더 많은 정보 제공 (객체니까)
    -예외객체를 보고 어떤 종류의 오류인지 알 수 있고
    -관련된 오류 메시지도 출력할 수 있다 
  • throw 키워드를 사용한다.
  • throws와 throw
    • throws : 예외처리 위임
      public void method() throws 예외1, 예외2 {
           예외1과 예외2가 발생되는 수행문들;   // 여기서 직접 처리하지 않고, 예외처리를 위임하기 위해서 throws 사용
      }

    • throw : 예외 강제 발생
      public void method( ) { 
          if ( score < 0 ) {           // 점수는 절대로 음수가 될 수 없다. 잘못된 경우가 true로 판정되면 
              throw new 예외클래스("오류메세지: 올바른 점수가 아닙니다.");    // 예외 강제발생 
           }
      }
      * 예외객체를 생성(예외객체의 생성자(메시지출력, cause)) -> 생성한 예외객체를 던짐
      * 강제로 발생시키는 예외는 자바에서 제공하는 예외클래스 혹은 사용자정의 예외클래스로 예외객체를 생성할 수 있다. 

 

 

public class ExceptionApp3 {

	public static void main(String[] args) {	
//		int score = -100;	// 오류 (원하는 값이 아님)
//		
//		if(score < 0) {
//			throw new Exception("점수가 올바르지 않습니다.");	// 예외 강제 발생
//		}
//		System.out.println("입력한 점수 : " + score);
//	}
	
		
		// 예외 일괄처리
		try {
			ExceptionApp3.method2(100, 100, 100);
			ExceptionApp3.method2(100, 100, -2);
		} catch(RuntimeException ex) {
			System.err.println("### 오류상황 발생");
			
			String errorMessage = ex.getMessage();
			System.err.println("### 오류 메세지: " + errorMessage);
			
			System.err.println("### 디버깅 정보 출력");
			ex.printStackTrace();
		}

	}
	
	// checkedException이 발생되는 메소드 
	// 이 메소드를 호출한 측에게 오류 발생 원인을 정확하게 전달할 수 있다. 
	public static void method1(int korScore, int engScore, int mathScore) throws Exception {
		if(korScore < 0) {
			throw new Exception("국어점수가 올바르지 않습니다.");	// 예외 강제 발생
		}
		if(engScore < 0) {
			throw new Exception("영어점수가 올바르지 않습니다.");	// 예외 강제 발생
		}
		if(mathScore < 0) {
			throw new Exception("수학점수가 올바르지 않습니다.");	// 예외 강제 발생
		}
		
		int total = korScore + engScore + mathScore;
		System.out.println("합계 : " + total);
	}
	
	// uncheckedException이 발생되는 메소드 
	// 예외를 강제 발생 시켜서 -> 이 메소드를 호출한 측에 오류발생 원인을 정확하게 전달할 수 있다. 
	public static void method2(int korScore, int engScore, int mathScore)  {
		if(korScore < 0) {
			throw new RuntimeException("국어점수가 올바르지 않습니다.");	// 예외 강제 발생
		}
		if(engScore < 0) {
			throw new RuntimeException("영어점수가 올바르지 않습니다.");	// 예외 강제 발생
		}
		if(mathScore < 0) {
			throw new RuntimeException("수학점수가 올바르지 않습니다.");	// 예외 강제 발생
		}
		
		int total = korScore + engScore + mathScore;
		System.out.println("합계 : " + total);
	}
	
	
	// 예외를 배우기 전 - 그냥 점수가 잘못됐는지 여부만 알 수 있음 (업무로직의 성공/실패 여부만 제공), 원인은 알 수 x  
	// 예외를 사용하면 -> 어디서 어떤 오류가 발생했는지를 알 수 있음
	public static boolean method3(int korScore, int engScore, int mathScore) {
		if (korScore < 0) {
			return false;
		}
		if (engScore < 0) {
			return false;
		}
		if (mathScore < 0) {
			return false;
		}
		int total = korScore + engScore + mathScore;
		System.out.println(total);
		return true;
		
	}
	
}

 

합계 : 300
### 오류상황 발생
### 오류 메세지: 수학점수가 올바르지 않습니다.
### 디버깅 정보 출력
java.lang.RuntimeException: 수학점수가 올바르지 않습니다.
	at day26.ExceptionApp3.method2(ExceptionApp3.java:58)
	at day26.ExceptionApp3.main(ExceptionApp3.java:18)

 

 

 

 

 

4) 사용자정의 예외클래스

 

  • 사용자정의 예외클래스를 정의하는 목적
    - 개발하는 애플리케이션에 대한 오류 정보를 표현하는 예외 클래스를 정의하기 위해서 
    - 개발하는 애플리케이션에서 발생하는 다양한 예외를 사용자정의 예외로 변환해서 예외처리를 단순화한다.
      (안그러면 발생하는 Exception마다 try catch가 엄청 늘어남 -> 하나의 예외처리로 단순화) 

 

*상속관계

CustomCheckedException -> Exception -> Throwable 

 

Throwable 클래스가 있고 

Exception 예외클래스는 Throwable을 상속받음

CustomCheckedException 예외클래스는 그 부모객체인 Exception을 상속받음

 

Exception에서는 super를 통해서 부모객체인 throwable의 생성자를 호출, 

실제로 에러와 관련된 정보는 모두 Throwable에 있음

 

CustomCheckedException에서도 supuer를 통해서 상속받은 부모객체인 exception의 생성자 호출 

 

private이라서 접근을 못해 , set도 없어 -> 생성자를 통해 접근 

super를 통해서 상위클래스의 생성자로 접근 -> throwable의 생성자로 최종 접근 가능 

 

throwable에 있는 메소드 두개도 모두 실행(출력)한 다음(=오류메시지 출력) 프로그램 종료

throw에 있는 변수 message : 오류 메시지, cause: 원인이 되는 오류 객체

 

 

내가 원하는 형태의 오류메시지를 넣을 수 있고,

다양한 에러를 잡고, 대신 내가 만든 오류객체 하나만 발생시킴 

일괄처리에서 처리해야할 예외의 개수가 확 줄어듦

오류메시지 분석

"yyyy-MM-dd" 형식의 텍스트가 아니네? 왜? 도대체 뭘 넣었길래? 

원인이 되는 객체 - "202223" 아 이렇게 넣었었구나

 

 

 

- CustomCheckedException

/*
 * 사용자정의 checked 예외 클래스
 * 	 Exception 클래스를 상속받는다.
 */

public class CustomCheckedException extends Exception {

	private static final long serialVersionUID = 1L;	//노란줄 없애기위함 시리얼넘버가 필요해서 만들었다 (나중에배움)

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

-CustomUncheckedException

/*
 * 사용자정의 unchecked 예외 클래스
 * 	RuntimeException 클래스를 상속받는다
 */

public class CustomeUncheckedException extends RuntimeException {

	private static final long serialVersionUID = 1L;

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

* 중간에 Runtime이 껴있으면 -> Unchecked,

                              안껴있으면 -> Checked 

 

 

 

 

-이렇게 하면 던져야될 예외가 너무 많아 (CustomeUncheckedException, NumberFormatException, ParseException)

import java.text.SimpleDateFormat;
import java.util.Date;

public class CustomExceptionApp1 {
	
	public static void main(String[] args) {
		
	}
	
    	// uncheckedException 발생 가능성이 있는 total메소드
	public static int total(int kor, int eng, int math) {	
		if(kor < 0) {
			throw new CustomeUncheckedException("국어점수가 올바르지 않습니다.");
		}
		if(eng < 0) {
			throw new CustomeUncheckedException("영어점수가 올바르지 않습니다.");
		}
		if(math < 0) {
			throw new CustomeUncheckedException("수학점수가 올바르지 않습니다.");
		}
		
		int total = kor + eng + math;
		return total; 
	}
	
	// NumberFormatException 발생 가능성이 있는 메소드
	public static int textToInt(String text) {
		return Integer.parseInt(text);  //text를 정수로 변환 (근데 숫자말고 다른게 들어있으면 예외발생) 
	}
	
	// ParseException 발생 가능성이 있는 메소드
	public static Date textToDate(String text) {
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
		Date date = sdf.parse(text);
		
		return date;
	}

}

 

 

-내가 정의한 예외만 던지면 됨 (CustomUncheckedException) 

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class CustomExceptionApp1 {	// 내가 정의한 한 종류의 예외만 발생하도록 하기 
	
	public static void main(String[] args) {
		
		int result1 = CustomExceptionApp1.total(100, 100, 100);
		System.out.println("합계: " + result1);
		int result2 = CustomExceptionApp1.total(-100, 100, 100);
		System.out.println("합계: " + result2);
		
		int result3 = CustomExceptionApp1.textToInt("1234");
		System.out.println("정수값: " + result3);
		int result4 = CustomExceptionApp1.textToInt("1234가가");	// numberformat예외가 발생할건데, 우리가 만든 예외객체에서 이걸 잡고 
		System.out.println("정수값: " + result4);
		
		Date result5 = CustomExceptionApp1.textToDate("2022-12-31");
		System.out.println("날짜: " + result5);
		Date result6 = CustomExceptionApp1.textToDate("20221231");
		System.out.println("날짜: " + result6);
		
	}
	
	public static int total(int kor, int eng, int math) {	// unchecked 예외를 발생시키는 total메소드
		if(kor < 0) {
			throw new CustomeUncheckedException("국어점수가 올바르지 않습니다.");
		}
		if(eng < 0) {
			throw new CustomeUncheckedException("영어점수가 올바르지 않습니다.");
		}
		if(math < 0) {
			throw new CustomeUncheckedException("수학점수가 올바르지 않습니다.");
		}
		
		int total = kor + eng + math;
		return total; 
	}
	
	// NumberFormatException 발생 가능성이 있는 메소드 -> CustomeUncheckedException 발생 가능성
	// 한종류의 예외만 발생시키도록 
	// 내가 만든 예외에 대한 정보 + 예외의 원인이 됐던 예외에 대한 정보 
	public static int textToInt(String text) {
		try {
			return Integer.parseInt(text);  //text를 정수로 변환 (근데 숫자말고 다른게 들어있으면 예외발생)
		} catch(NumberFormatException cause) {	// number예외는 여기서 잡아버리고 unchecked를 대신 던져버려. 원인이 되는 오리지널 Exception 
			throw new CustomeUncheckedException("숫자가 아닌 문자가 포함되어 있습니다.", cause);
		}
	}
	
	// ParseException 발생 가능성이 있는 메소드 -> CustomeUncheckedException 발생 가능성 
	public static Date textToDate(String text) {
		try {
			SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
			Date date = sdf.parse(text);
			
			return date;
		} catch (ParseException cause) {	// parse 예외는 여기서 잡아버리고, unchecked를 발생시킴
			throw new CustomeUncheckedException("yyyy-MM-dd 형식의 텍스트가 아닙니다.", cause);
		}
	}

}

 

*unchecked 예외를 발생시키는 이유

: checked를 발생시키면, 예외처리를 반드시 해야해서 -> throws를 다 붙여줘야함 

그럼 던질게 너무 많아져

unchecked는 안적어도 던져짐

 

 

합계: 300
Exception in thread "main" day26.CustomeUncheckedException: 국어점수가 올바르지 않습니다.
	at day26.CustomExceptionApp1.total(CustomExceptionApp1.java:30)
	at day26.CustomExceptionApp1.main(CustomExceptionApp1.java:13)

 

정수값: 1234
Exception in thread "main" day26.CustomeUncheckedException: 숫자가 아닌 문자가 포함되어 있습니다.
	at day26.CustomExceptionApp1.textToInt(CustomExceptionApp1.java:50)
	at day26.CustomExceptionApp1.main(CustomExceptionApp1.java:18)
Caused by: java.lang.NumberFormatException: For input string: "1234가가"
	at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67)
	at java.base/java.lang.Integer.parseInt(Integer.java:668)
	at java.base/java.lang.Integer.parseInt(Integer.java:786)
	at day26.CustomExceptionApp1.textToInt(CustomExceptionApp1.java:48)
	... 1 more

 

날짜: Sat Dec 31 00:00:00 KST 2022
Exception in thread "main" day26.CustomeUncheckedException: yyyy-MM-dd 형식의 텍스트가 아닙니다.
	at day26.CustomExceptionApp1.textToDate(CustomExceptionApp1.java:62)
	at day26.CustomExceptionApp1.main(CustomExceptionApp1.java:23)
Caused by: java.text.ParseException: Unparseable date: "20221231"
	at java.base/java.text.DateFormat.parse(DateFormat.java:399)
	at day26.CustomExceptionApp1.textToDate(CustomExceptionApp1.java:58)
	... 1 more