수업내용/Java

[2022.10.07.금] 예외처리

주니어주니 2022. 10. 7. 21:54

 

 

 

1. 예외처리

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

 

 

1-1. 오류의 종류

 

 

 

1) 에러(Error)

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

 

2) 예외(Exception) 

  • 개발자의 코딩실수나 사용자의 잘못된 프로그램 사용으로 발생하는 오류
  • 예외는 예외처리를 통해서 비정상적인 종료를 예방할 수 있다.
  • 예외는 UncheckedException과 CheckedException으로 구분한다.
    * 최신의 라이브러리나 프레임워크는 대부분 UncheckedException을 사용

 

 

UncheckedException

  • RuntimeException 클래스와 그 자식 클래스들이다.
  • 주로 개발자의 코딩 실수로 발생되는 오류들이다.
  • 예외처리 안해도됨
  • 컴파일러가 예외처리 여부를 체크하지 않는다.
  • 주요 예외 클래스
    • RunTimeException
      모든 UnChecked Exception의 부모 클래스다.
    • NullPointerException
      참조변수의 값이 null인 상태에서 필드나 메소드를 사용할 때 발생하는 예외 클래스다.
    • ClassCastException
      클래스 형변환이 가능하지 않을 때 발생하는 예외 클래스다.
    • ArithmeticException
      나눗셈에서 어떤 값을 0으로 나눌 때 발생하는 예외 클래스다.
    • IndexOutOfBoundsException
      배열, 리스트, 문자열에서 인덱스 범위를 벗어난 위치를 조회했을 때 발생하는 예외 클래스다.
    • NumberFormatException
      숫자가 아닌 문자를 포함하고 있는 문자열을 정수나 실수로 변환할 때 발생하는 예외 클래스다.
      Integer.parseInt(s), Double.parseDouble(s) 등을 실행할 때 발생한다.

 

CheckedException

  • Exception 클래스와 Exception 클래스의 하위 클래스중에서 RuntimeException 클래스의 하위 클래스가 아닌 예외클래스다.
  • 사용자의 잘못된 사용으로 인해 발생하는 오류들이다.
  • 예외처리 반드시 필요 
  • 컴파일러가 예외처리 구현 여부를 반드시 체크한다.
    • 예외처리 관련 코드가 구현되어 있지 않으면 컴파일 오류가 발생한다.
    • 최신의 라이브러리나 프레임워크에서는 CheckedException의 사용비중이 점점 줄어들고 있다.
  • 주요 클래스
    • Exception
      모든 Checked Exception의 부모 클래스다.
    • ParseException
      올바른 형태를 입력하지 않았을 때 발생하는 예외클래스다. 
    • ClassNotFoundException
      클래스파일을 찾을 수 없을 때 발생하는 예외클래스다.
    • IOException
      읽기/쓰기 하는 도중 오류가 발생했을 때 발생하는 예외클래스다.
      네트워크를 통해서 다른 컴퓨터와 데이터 교환중 오류가 발생했을 때 발생하는 예외클래스다.
    • FileNotFoundException
      파일을 찾을 수 없을 때 발생하는 예외 클래스다.
    • SQLException
      데이터베이스 엑세스 작업 중 오류가 발생했을 때 발생하는 예외클래스다.

 

 

 

 

* 오류의 발생

 

자바가상머신은 발생가능한 오류에 대한 다양한 예외클래스를 가지고 있음 

-> 자바가상머신의 실행프로그램에서 메소드를 실행하다가 오류가 발생

-> 오류 상황에 맞는 예외클래스로 예외객체 생성

-> 예외 객체 안에 오류메세지(오류상황과 관련된 오류 정보)를 담아서

-> 이 예외객체를 자바가상머신에 전달 (throws : 오류를 던지다)

-> 오류 메세지를 출력하고 프로그램 종료 

=> 예외 발생

 

예외를 던지면 (throw) -> 잡아야 (catch) -> 예외를 가로채서 방지 (JVM에 안가도록) 

=> 예외 처리

 

오류 발생하면 -> db 엑세스, 업무로직에서 각각 처리하지 않고, 한군데에서 오류 페이지를 보냄 (일괄예외처리) 

 

 

 

 

  • UncheckedException이 발생하는 예 -> 예외처리 해도되고 안해도됨
public class ExceptionApp1 {

	public static void main(String[] args) {
		
		// UncheckedException이 발생하는 예 -> 오류 발생해도 미리 안알려줌
		System.out.println("### 프로그램 시작");
		int number1 = Integer.parseInt("1234");		// 형식에 맞게 적음
		System.out.println("첫번째 숫자: " + number1);
		
		int number2 = Integer.parseInt("1234A");	// 형식에 맞지 않게 적음 -> 빨간줄이 안가
		System.out.println("두번째 숫자: " + number2);
		
		System.out.println("### 프로그램 종료");
	}
}

 

### 프로그램 시작
첫번째 숫자: 1234
Exception in thread "main" 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 day25.ExceptionApp1.main(ExceptionApp1.java:12)

 

1) 어떤 오류 발생? -> java.lang.NumberFormatException

숫자형식이 아니다 

parseInt -> NumberFormatException (-> UncheckedException) 을 던짐

 

2) 어떤 원인으로 발생? -> For input string: "1234A"

이거 때문에 

 

3) 어디에? -> ExceptionApp1.java:12

: 위에서 아래 방향으로 내가 작성한 클래스를 찾아 (내가 작성한게 아니면 수정할 수가 없어, 내가 작성한 클래스의 몇번째 줄에 오류가 있는지 확인) 

 

 

 

 

  • checkedException이 발생하는 예 -> 에외처리를 무조건 해야함 
import java.text.SimpleDateFormat;
import java.util.Date;

public class ExceptionApp2 {	

	public static void main(String[] args) {
		
		// checkedException이 발생하는 예 -> 에외처리를 안하면 아예 오류 
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
		
		Date date1 = sdf.parse("2022-12-31");		// 형식에 맞게 적어도 -> 오류 
		System.out.println("첫번째 날짜: " + date1);
		
		Date date2 = sdf.parse("20221231");		// 형식에 맞지 않게 적음 -> 명백한 오류
		System.out.println("첫번째 날짜: " + date1);
	}
}

 

Exception in thread "main" java.lang.Error: Unresolved compilation problems: 
	Unhandled exception type ParseException
	Unhandled exception type ParseException

	at day25.ExceptionApp2.main(ExceptionApp2.java:13)

 

parse -> ParseException (-> CheckedException) 을 던짐 

 

 

 

 

* UncheckedException과 CheckedException의 구분 

 

 

따라가다보면

CheckedException의 상위는 Exception

UncheckedException의 상위는 Runtime

 

최상위 클래스는 Throwable 클래스 

 

부모가 Runtime이면 전부다 UncheckedException -> 예외처리 안해도 됨

부모가 Parse면 CheckedException -> 예외처리 필수 

 

이 parse가 오류가 발생할 수 있는 메소드인데 컴파일러가 확인을 안해

오류 체크를 안해 그냥 실행이 돼 (예외처리 했냐안헀냐 안따져)  -> unchecked 

예외가 발생할 수 있는데 예외처리 코드 안적어놓으면 오류 -> checked

 

 

 

1-2. 예외처리

 

  1. try ~ catch 구문을 사용해서 그 자리에서 바로 예외를 처리할 수 있다.
  2. throws를 사용해서 예외처리를 위임할 수 있다.

 

 

1) try ~ catch

 

  • try 블록에서는 예외발생이 예상되는 수행문을 적는다.
  • catch 블록은 try 블록에서 예외가 발생했을 때 해당 예외를 잡는다.
  • catch 블록에서 예외를 잡지않으면 프로그램이 비정상적으로 종료된다.
  • try 블록에서 여러 종류의 예외발생이 예상되는 수행문을 적었을 경우에는 그 예외의 종류만큼 catch 블록을 추가한다.
  • catch 블록에서는 발생한 예외를 잡고, 그 예외발생시 실행할 수행문을 적는다.
  • catch 블록에서는 작업내용
    • 발생한 예외정보를 로그로 기록하기
    • 사용자에게 에외발생원인 안내하기
    • 개발자에게 오류 수정을 위한 디버깅메세지 출력하기
    • 발생한 예외를 다른 예외로 바꾸기
  • catch 블록을 작성할 때는 부모예외클래스를 잡는 catch블록을 아래쪽에 적는다.
  • 맨 마지막 catch 블록에는 Exception 클래스를 지정해서 예상하지 못한 예외도 잡을 수 있도록 한다.
try {     

           //예외발생할 가능성이 있는 문장

} catch(Exception1 e1) { 
    // (내가 잡을 예외의 클래스명 변수)

           //Exception1이 발생했을 경우, 이 예외를 처리하기 위한 문장
           //보통 이곳에 예외메세지를 출력하고 로그로 남김.

} catch(Exception2 e2) {


           //Exception2이 발생했을 경우, 이를 처리하지 위한 문장적는다.

 

  • try ~ catch 구문으로 에외처리시 수행문 실행
try {
     예외발생이 예상되는 수행문1;
     수행문2;
     수행문3; 
} catch (발생이예상되는예외클래스명 변수명) {
     예외 발생시 실행될 수행문4;
     예외 발생시 실행될 수행문5;
} 수행문6;
  • 수행문1에서 예외발생하지 않는 경우 실행되는 수행문
    1. 예외발생이 예상되는 수행문1
    2. 수행문2
    3. 수행문3
    4. 수행문6
  • 수행문1에서 예외가 발생하는 경우 실행되는 수행문
    1. 예외발생이 예상되는 수행문1
    2. 예외 발생시 실행될 수행문4
    3. 예외 발생시 실행될 수행문5
    4. 수행문6

 

 

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

public class ExceptionApp2 {	

	public static void main(String[] args) {
		
		// checkedException이 발생하는 예 -> 에외처리를 안하면 아예 오류 
		System.out.println("## 프로그램 시작");
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
		
		try {
			System.out.println("### 예외처리 발생이 예상되는 수행문 실행 시작");
			
			Date date1 = sdf.parse("2022-12-31");		
			System.out.println("첫번째 날짜: " + date1);
			Date date2 = sdf.parse("20221231");	// 여기서 예외 발생 -> catch로 감
			System.out.println("두번째 날짜: " + date2);
			
			System.out.println("### 예외처리 발생이 예상되는 수행문 실행 종료");
		} catch (ParseException ex) {			// ParseException을 가로채고 그 예외가 변수 ex에 들어감
			System.out.println("### 가로챈 예외: " + ex);
			System.out.println("### 발생한 예외를 가로채고, 예외처리 작업을 수행");
		}
		System.out.println("### 프로그램 종료");
	}
}

 

## 프로그램 시작
### 예외처리 발생이 예상되는 수행문 실행 시작
첫번째 날짜: Sat Dec 31 00:00:00 KST 2022
### 가로챈 예외: java.text.ParseException: Unparseable date: "20221231"
### 발생한 예외를 가로채고, 예외처리 작업을 수행
### 프로그램 종료

 

 

 

try {

Date date2 = sdf.parse("20221231"); 

여기서 예외가 발생 -> catch문장 실행 (try안에서 예외가 발생했을 때만 catch 실행)

 

} catch (ParseException ex) {

-> catch에서 ParseException을 잡아냄 -> 잡아낸 예외가 변수 ex에 들어감

타입에 실제로 발생한 예외 or 그 예외의 부모 예외객체를 적어도 됨  ( = Exception ) 

-> 일괄처리할 때 부모객체를 적어두면 예상하지 못했던 예외들도 잡을 수 있음 ( 부모객체니까 다 담겨서 ) 

-> 그래서 항상 맨 마지막에 보험으로 적어둠

 

ex)

*오류: 네트워크 접속사가 많아서 연결이 되지 않음 ( 오류의 원인 제시 ) 

*오류: 사용자 아이디가 일치하지 않음 ( 오류의 원인 제시 ) 

 

*오류: 알 수 없는 이유로 연결할 수 없음 ( 오류의 원인 알 수 없을 때 ) 

 

-> 각각의 오류페이지를 다 준비를 해놓는데, 만약 예상치 못한 오류라면 마지막에 저렇게 일괄처리

 

 

 

 

 

 

2) throws 로 예외처리 위임하기 

  • 메소드에서 발생하는 예외를 직접 처리하지 않고, 그 메소드를 호출하는 측에서 예외처리를 위임하는 것이다.
  • throws 키워드를 사용해서 예외처리를 위임할 수 있다.
  • 예외가 발생하는 메소드에서 예외처리와 관련한 코드의 작성을 할 필요가 없다.
  • 예외처리를 각각의 메소드에서 직접 개별적으로 처리하지 않고, 한 군데에서 일괄처리하게 만들 수 있다.
  • 예외를 위임하기
public void method() throws 예외클래스명, 예외클래스명, ... {
     예외발생이 예상되는 수행문;
     예외발생이 예상되는 수행문;
}

 

* method4에서 yyyException이 발생

-> method4를 호출한 method2로 yyyException에 대한 예외처리를 대신 하라고 던짐 

-> method2를 호출한 곳으로  yyyException에 대한 예외처리를 대신 하라고 던짐

-> yyyException예외를 catch해서 예외처리를 실행

 

-> 이렇게 하면 호출하는 메소드가 많아질 수록, throws하는 예외객체가 많아짐

    ( void method5() throws xxxException, yyyException, zzzException, ... ) 

-> unchecked (RuntimeException) -> 내가 throws를 적지 않아도 알아서 던짐 !!!

 

 

import java.io.FileWriter;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class ExceptionApp3 {

	public static void main(String[] args) {
		try {	// 예외를 일괄처리 -> 그럼 각각의 메소드에서 예외를 처리할 필요가 없음
        		// Exception이 발생하는 거를 2개나 호출할거야 -> 일괄처리 
			Date date = ExceptionApp3.stringToDate("2012-12-31");	// 얘가 stringToDate를 호출 -> 밑에서 throws를 던져서 얘가 예외를 처리하게 됨 
			System.out.println(date);
			
			ExceptionApp3.writeText("안녕하세요");	// 얘가 writeText를 호출 -> 얘가 예외를 위임하게 됨
            
		} catch (ParseException ex) {
			System.out.println("### 올바른 날짜 형식의 문자열이 아닙니다.");
		} catch (IOException ex) {
			System.out.println("### 입출력 작업 중 오류가 발생하였습니다.");
		} catch (Exception ex) {
			System.out.println("### 알 수 없는 오류가 발생하였습니다.");
		}
	}
	
	// 예외처리를 이 메소드를 호출하는 측에게 위임함 (stringToDate를 호출한 메소드가 throws를 맞음) 
	public static Date stringToDate(String text) throws ParseException {
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
		Date date = sdf.parse(text); 
		
		return date;
	}
	
	public static void writeText(String text) throws IOException {
		FileWriter writer = new FileWriter("sample.text");
		writer.write(text);
		writer.flush();
		writer.close();
        
        	System.out.println(text);
	}
	
}

 

Mon Dec 31 00:00:00 KST 2012
안녕하세요