본문 바로가기
☁︎KOSTA/☂︎KOSTA-JAVA

[KOSTA] Spring 기반 Cloud 서비스 구현 개발자 양성 (Day 14) - Exception Handling (예외처리)

by 하_센세 2022. 8. 17.

2022.08.17 [WED]

- Day 14- 

 

수업 주요 목차

  • Exception Handling (예외처리)
    • try
    • catch
    • finally
    • throws
    • throw

🤖Review

 


 

Exception Handling (예외처리) : 프로그램 수행 중 exception 발생시 예외대처흐름/대처방안(a.k.a.handler logic)을 실행하고 프로그램을 정상 실행하는 것

            👾 JVM이 프로그램 실핼 중 Exception을 발생시키고 별도의 Handling이 없으면 기본적으로 예외발생 즉시 종료가 된다.

                    ex.) 배가 아파서(Exception : 예외) 약을 먹고(Handling : 처) 공부를 계속한다.(정상실행)

 

  • try : Exception 발생 예상 영역 지정
  • catch : Exception Handling (예외처리) Exception 발생시 대처 방안 실행 후 정상 수행이 목적 / 하나의 try, 다양한 catch 가능
  • finally :  Exception 발생/처리 여부와 관계없이 항상 처리 ( ex. 무조건 카드를 돌려준다.)
  • throws : Exception을 호출한 측으로 던질 수 있다는 의미 (method 선언시 사용)
  • throw : Exception을 필요시 고의로 발생시킨다는 의미

 

Checked Exception 계열 UnChecked Exception 계열
Runtime계열 Exception을 제외한 모든 Exception  RuntimeException 클래스의 자식 Exception class들
컴파일 시점에서 Exception처리를 체크할 수 있는 Exception 계열 대표적으로 NullPointerException, IndexOutOfBoundsException, NumberFormatException 등
Exception 처리는 try~catch 또는 throws로 선택지가 있다. method에서 throws를 명시하지 않아도 Runtime계열의 Exception은 자동으로 throws 된다.(호출한 측으로 전파된다.)

 

   

 

 


 

🔎 Eclipse 실습 내용

 

1. Exception 1

 

 java application 실행시 jvm은 Exception 상황을 만나면 해당 Exception(아래 경우는 NullPointerException)을 발생시킨 후 별도의 처리(Handler)가 없다면 즉지 메세지 출력 후 비정상 종료된다.

package step1;

public class TestException1 {
	public static void main(String[] args) {
		System.out.println("프로그램 실행시작");
		String name="아이유";
		name=null; //고의로 null을 할당해 Exception 발생되도록 해본다.
		System.out.println(name.length()+"자로 이름이 구성됨"); //실행내용(business logic)
		System.out.println("프로그램 정상수행");
	}
}

프로그램 실행시작
Exception in thread "main" java.lang.NullPointerException
	at step1.TestException1.main(TestException1.java:12)

 

 

2. Exception 2


  Exception Handling을 통해 Exception 발생시 대처방안을 실행하고 프로그램이 정상수행되도록 한다.

package step1;

public class TestException2 {
	public static void main(String[] args) {
		System.out.println("프로그램 실행시작");
		String name="아이유";
		name=null; //고의로 null을 할당해 Exception 발생되도록 해본다.
	try{  // try 구문은 Exception발생 예상영역 지정
		System.out.println(name.length()+"자로 이름이 구성됨"); //실행내용(business logic)
	} catch(NullPointerException ne) { // catch 구문은 예외를 처리하는 영역(Exception Handling)
		System.out.println(ne.getMessage()+": 예외처리 => 이름 정보가 존재하지 않아 길이를 구할 수 없습니다.");
		ne.printStackTrace(); // Exception 발생경로,  종류를 모두 출력
	}
		System.out.println("프로그램 정상수행");
	}
}

프로그램 실행시작
null: 예외처리 => 이름 정보가 존재하지 않아 길이를 구할 수 없습니다.
java.lang.NullPointerException
	at step1.TestException2.main(TestException2.java:19)
프로그램 정상수행

예제 1과 2의 차이점은 Exception Handling을 통해 프로그램이 정상수행이 여부이고 이것이 포인트

 

3. Exception 3 : Exception 1처럼 Exception을 고의로 발생시켜 비정상 종료되는 예제

package step2;

import java.util.ArrayList;

public class TestException3 {
	public static void main(String[] args) {
		ArrayList<String> list=new ArrayList<>();
		list.add("짜장면");
		list.add("탕수육");
		System.out.println(list.get(2)); // JVM이 실행될 때만 알 수 있는 에러
		System.out.println("정상수행");
	}
}

Exception in thread "main" java.lang.IndexOutOfBoundsException: Index: 2, Size: 2
	at java.util.ArrayList.rangeCheck(ArrayList.java:659)
	at java.util.ArrayList.get(ArrayList.java:435)
	at step2.TestException3.main(TestException3.java:12)

 

 

 

4. Exception 4 : Exception3을 보완하여 Exception Handling 한다.(예외 처리)

 

package step2;

import java.util.ArrayList;

public class TestException4 {
	public static void main(String[] args) {
		//TestException3에서는 IndexOutOfBoundsException을 고의로 발생시켜 비정상 종료되는 예제
		// TestException4에서 Exception Handling 한다.(예외 처리)
		ArrayList<String> list=new ArrayList<>();
		list.add("짜장면");
		list.add("탕수육");
		String name=null;
		try {
			System.out.println(name.length()); //발생되는 즉시 건너 뛰어 catch로 감
			System.out.println(list.get(2));	
			System.out.println("A");
		} catch(IndexOutOfBoundsException ie){ // 해당되는 exception이 아니라서 건너 뛰어 아래 catch로 감
			// 구체적인 타입으로 처리하면 해당 exception에 대한 상세한 대처가 가능 (자식->부모 순서로 작성해야 함)
			System.out.println("B");
			System.out.println(ie.getMessage()+" 리스트 요소 범위 초과하여 요소를 반환할 수 없습니다 : ex)팝업");
		}	catch(Exception e){ //부모타입 Exception으로도 다양한 자식 Exception을 처리할 수 있음
			System.out.println("C");
			e.printStackTrace();
		}
		System.out.println("정상수행");
		System.out.println("D");
	}
}

이 코드 블럭에서 발생할 수 있는 오류가 여러가지 통증을 최대한 구체적으로(상세하게) 처리하는 것이 효율적인 것처럼 최후의 보루로 남겨두고 자식객체로 처리하는 것을 추천

C
java.lang.NullPointerException
	at step2.TestException4.main(TestException4.java:14)
정상수행
D

 

 

5. Finally : finally를 테스트하기 위한 예제


  고의로 예외 처리에 실패하고 예외 처리에 실패해도 finally구문은 언제나 실행한다.
       (ex. 현금 인출기는 어떤 상황이 벌어져도 입력받은 고객의 카드를 반드시 마지막에 되돌려주도록 처리한다.)

package step3;

public class TestFinally {
	public static void main(String[] args) {
		String age="스물";
		try {
			int iage=Integer.parseInt(age); //문자열을 정수형으로 변환
			System.out.println(iage+1); // 정수 변환이 정상적으로 된 후의 후속작업이므로 try블록 안에 위치
		} catch(NullPointerException ne) {
			System.out.println(ne.getMessage()+"이므로 출력불가"); // NumberFormatException 발생하는데 다른 예외를 고의로 처리해본다. -> 처리안됨
		} finally { // 현 위치에 finally 블록을 쓰는 것과 안 쓰는 것의 차이 -> Exception Handling에 실패하더라도 finally구문은 실행
			System.out.println("finally 영역 : 예외 발생 및 처리여부와 관계없이 항상 실행");
			
		}
		System.out.println("정상수행");
	}
}

finally 영역 : 예외 발생 및 처리여부와 관계없이 항상 실행
Exception in thread "main" java.lang.NumberFormatException: For input string: "스물"
	at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
	at java.lang.Integer.parseInt(Integer.java:580)
	at java.lang.Integer.parseInt(Integer.java:615)
	at step3.TestFinally.main(TestFinally.java:13)

 

 

6. try catch finally review 1
 * 실행결과를 예상해본다.

package step4;

import java.util.ArrayList;

public class TestReviewException {
	public static void main(String[] args) {
		System.out.println("A");
		try {
			ArrayList<Integer> list=new ArrayList<>();
			System.out.println(list.get(0)); // IndexOutOfBoundException 발생
			System.out.println("B");
		} catch(NullPointerException ne) {
			System.out.println("C");
		} finally {
			System.out.println("D");
		}
		System.out.println("E");
	}
}

A
D
Exception in thread "main" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
	at java.util.ArrayList.rangeCheck(ArrayList.java:659)
	at java.util.ArrayList.get(ArrayList.java:435)
	at step4.TestReviewException.main(TestReviewException.java:14)

 

 

7. try catch finally review 2
 * 실행결과를 예상해본다.

package step4;

import java.util.ArrayList;

public class TestReviewException2 {
	public static void main(String[] args) {
		System.out.println("A");
		try {
			ArrayList<Integer> list=new ArrayList<>();
			System.out.println(list.get(0)); // IndexOutOfBoundException 발생
			System.out.println("B");
		} catch(RuntimeException re) { // IndexOutOfBoundException의 부모
			System.out.println("C");
		} finally {
			System.out.println("D");
		}
		System.out.println("E");
	}
}

A
C
D
E

 

 

8. Throws 1

package step5;

import java.io.FileNotFoundException;
import java.io.FileReader;

public class TestThrows1 {
	public static void main(String[] args) {
		try {
			FileReader fr = new FileReader("/Users/hasense/kosta250/se-workspace/a.rtf"); 
//			FileReader fr = new FileReader("/Users/hasense/kosta250/se-workspace/a2.rtf"); 
																								
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("정상수행");
	}
}

 // 파일이 실제로 존재하면 정상적으로 입력작업처리, 파일이 존재하지 않으면 FileNotFoundException이 발생되어 throws되고 catch이 실행된다.

// java se api에서 제공하는 FileReader 클래스의 생성자에서 throws하는 케이스

정상수행


//파일이 있을 경우 실행결과



java.io.FileNotFoundException: /Users/hasense/kosta250/se-workspace/a2.rtf (No such file or directory)
	at java.io.FileInputStream.open0(Native Method)
	at java.io.FileInputStream.open(FileInputStream.java:195)
	at java.io.FileInputStream.<init>(FileInputStream.java:138)
	at java.io.FileInputStream.<init>(FileInputStream.java:93)
	at java.io.FileReader.<init>(FileReader.java:58)
	at step5.TestThrows1.main(TestThrows1.java:10)
정상수행



//파일이 없을 경우 실행결과

 

 

9. Throws 2

package step5;

import java.io.FileNotFoundException;
import java.io.FileReader;

public class TestThrows2 {
	public static void main(String[] args) throws FileNotFoundException {
//main에서 throws를 한다는 것은 전달하겠다는 의미(예외처리를 하지 않겠다는 의미, 단순히 CheckedException에 의한 compile error를 피하는 용도)
		FileReader fr = new FileReader("/Users/hasense/kosta250/se-workspace/a2.rtf");
		System.out.println(fr+"file read");
		System.out.println("정상수행");
	}
}

Exception in thread "main" java.io.FileNotFoundException: /Users/hasense/kosta250/se-workspace/a2.rtf (No such file or directory)
	at java.io.FileInputStream.open0(Native Method)
	at java.io.FileInputStream.open(FileInputStream.java:195)
	at java.io.FileInputStream.<init>(FileInputStream.java:138)
	at java.io.FileInputStream.<init>(FileInputStream.java:93)
	at java.io.FileReader.<init>(FileReader.java:58)
	at step5.TestThrows2.main(TestThrows2.java:9)

 

 

10. Throws 3

package step6;

import java.io.FileReader;
import java.io.IOException;

public class MemoService {
	public String readMemo(String fileName) throws IOException {
		//파일을 입력받기 위한 클래스
		FileReader fr=null;
		try {
			fr=new FileReader(fileName); //파일이 없는 경우 만들어지지 않았기 때문에 fr=null
			System.out.println("A");			
		} finally {
			if(fr!=null) //null이 아닐 때
			fr.close();
			System.out.println("D");
		}
			return fileName+"에 기록된 정보"; // 정상 흐름일 때 동작
	}
}

-메인

package step6;

import java.io.FileNotFoundException;
import java.io.IOException;

public class TestThrows3 {
	public static void main(String[] args) {
		MemoService service=new MemoService();
		// A B C  출력결과를 예상해본다.
		String fileName="/Users/hasense/kosta250/se-workspace/a.rtf"; // 존재하는 파일, 정상처리
		fileName="/Users/hasense/kosta250/se-workspace/a2.rtf"; // 존재하지 않는 파일, 예외
		String result;
		try {
			result = service.readMemo(fileName);
			// 위 read method가 정상 흐름일 때 아래 후속 작업을 진행한다.
			System.out.println("읽은 내용: "+result);
			System.out.println("B");
		} catch (FileNotFoundException e) {
			System.out.println("C");
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		System.out.println("정상수행");
	}
}

A
D
읽은 내용: /Users/hasense/kosta250/se-workspace/a.rtf에 기록된 정보
B
정상수행

// 파일이 있는 경우

D
C
java.io.FileNotFoundException: /Users/hasense/kosta250/se-workspace/a2.rtf (No such file or directory)
	at java.io.FileInputStream.open0(Native Method)
	at java.io.FileInputStream.open(FileInputStream.java:195)
	at java.io.FileInputStream.<init>(FileInputStream.java:138)
	at java.io.FileInputStream.<init>(FileInputStream.java:93)
	at java.io.FileReader.<init>(FileReader.java:58)
	at step6.MemoService.readMemo(MemoService.java:11)
	at step6.TestThrows3.main(TestThrows3.java:14)
정상수행

//파일이 없는 경우

 

 

11. Throws 4

//throws 사례 : DB 설치되어 있지 않고 URL이 없으므로 아래 코드는 findEmployee() method의 getConnection 부분에서 SQLException이 발생된다.
// 이 때 코드의 실행 흐름을 예상해보는 예제
//현재 코드는 Exception 관련 keyword를 확인하는 예제이므로 IO또는 DB관련 코드는 이후 자세히 설명 

package step7;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

// DAO : Data Access Object
public class EmployeeDAO {
	public String findEmployee(String url,String id) throws SQLException {
		String result=null;
		Connection con=null;
		//database에 연결하기 위한 객체
		try {
			con=DriverManager.getConnection(url);
			System.out.println("C");
		} finally {
			if(con!=null) //con이 null이 아니고 존재하면 반드시 close 처리한다.
			con.close();
			System.out.println("D");
		}
		System.out.println("E");
		return result;
	}
}

-메인

package step7;

import java.sql.SQLException;

public class TestThrows4 {
	public static void main(String[] args) {
		EmployeeDAO dao=new EmployeeDAO();
		try {
			String result=dao.findEmployee("java","db url");
			System.out.println("db연동 결과: "+result);
			System.out.println("A");
		} catch(SQLException e) {
			System.out.println("B");
			e.printStackTrace();
		}
	}
}

D
B
java.sql.SQLException: No suitable driver found for java
	at java.sql.DriverManager.getConnection(DriverManager.java:689)
	at java.sql.DriverManager.getConnection(DriverManager.java:270)
	at step7.EmployeeDAO.findEmployee(EmployeeDAO.java:14)
	at step7.TestThrows4.main(TestThrows4.java:12)

 

 

12. Throw 1

package step8;

import java.io.IOException;

//Throws : Exception 발생시 호출한 쪽으로 전파 또는 위임
// Throw : Exception 발생시킬 때 사용
class Service{
	public void test1(boolean flag) { // Runtime계열 exception(Unchecked Exception)은 별도로 throws가 명시 필요 없음
		if(flag)
			throw new NullPointerException();
		System.out.println("throw test");
	}
	public void test2(boolean flag) throws IOException { // checked exception이라서 별도의 throws or try catch가 필요
		if(flag) // throw는 예외를 발생시킬 때 사용하는 키워드
			throw new IOException();
		System.out.println("throw test");
	}
}
public class TestThrow1 {
	public static void main(String[] args) {
		Service service=new Service();
//		try {
//		service.test1(true);
//		} catch (NullPointerException ne) {
//			System.out.println("test1 예외발생에 대해 처리");
//		}
		//service.test1(true); // unchecked - 별도의 throws가 명시 필요 없음
		try {
			service.test2(true);
		} catch (IOException e) {
			System.out.println("test2 예외발생에 대해 처리");
		} 
		System.out.println("정상수행");
	}
}

test1 예외발생에 대해 처리
Exception in thread "main" java.lang.NullPointerException
	at step8.Service.test1(TestThrow1.java:10)
	at step8.TestThrow1.main(TestThrow1.java:27)

//////////////////////////////////////////////////////////

test2 예외발생에 대해 처리
정상수행

 

 

13. Throw 2 

// 직접 예외 클래스를 정의 : java.lang.Exception을 상속받으면 된다
// serialVersionUID부분은 이후 객체직렬화에서 공부 예정 (지금은 몰라도 됨)

package step8;
public class AgeException extends Exception {
	private static final long serialVersionUID = 220838069605410987L; 
	public AgeException() {
		super("나이 정보를 다신 확인하세요."); // 상속받은 부모 Exception 클래스의 생성자 호출해 message 할당, 이후 ㅊatch 구문에서 getMessage()로 확인 가능
	}
	public AgeException(String message) { 
	super(message);
	}
}
package step8;

public class MovieService {

	public void enterAdultMovie(int age) throws AgeException {
		if(age<=0) // 잘못된 나이 정보면 예외 발생
			throw new AgeException(age+"세는 잘못된 나이 정보입니다");
		if(age<=19) // 미성년자이면 성인영화이므로 예외 발생
			throw new  AgeException(age+"세 미성년이므로 관람 불가합니다.");
		System.out.println("나이 확인되었습니다.");
	}
}

-메인

package step8;

public class TestThrow2 {
	public static void main(String[] args) {
		MovieService service=new MovieService();
		int age=-1; // 잘못된 나이정보
		age=13; //13세 미성년 예외
		age=20; // 20이상 정상수행
		try {
		service.enterAdultMovie(age);
		} catch(AgeException ae) {
			System.out.println(ae.getMessage());
		}
		System.out.println("정상수행");
	}
}

-1세는 잘못된 나이 정보입니다
정상수행
/////////////////////////////
13세 미성년이므로 관람 불가합니다.
정상수행
/////////////////////////////
나이 확인되었습니다.
정상수행

 

14.


 // 생일은 1 이상 31이하일 때만 할당
 // 그 외의 정보는 DayException throw, throws 발생시키고 전달하겠다는 의미

package step9;

public class DayException extends Exception {
	private static final long serialVersionUID = 4105410795203597452L; // 이후 공부예정
	public DayException(String message) {
		super(message);
	}

}
package step9;

public class MyDate {
	private int birthDay;
	public MyDate(int birthDay) throws DayException{
		this.birthDay=birthDay;
		if(1>birthDay||birthDay>31)
			throw new DayException(birthDay+" 잘못된 day 정보입니다.");
	}
	public int getBirthDay() {
		return birthDay;
	}

}

 

-메인

package step9;

public class TestThrow3 {
	public static void main(String[] args) {
		int birthDay=5; // 정상흐름
		birthDay=0; //예외흐름
		birthDay=32; //예외흐름
//		MyDate 	date;  //try 문 안에서 생
		try {
			MyDate date = new MyDate(birthDay);  
			System.out.println(date.getBirthDay()); // sysout을 안해줬어서 정상흐름이 출력 안되었었음!
		} catch (DayException e) {
			System.out.println(e.getMessage());
		}
//32 잘못된 day 정보입니다.
//or 0잘못된 day 정보입니다.
	}
}

5
//////////////////////////////////////
0 잘못된 day 정보입니다.
//////////////////////////////////////
32 잘못된 day 정보입니다.

 

 

MyDate 클래스는 깔끔하게 성공!

main에서는 sysout을 안써줘서 5(정상흐름)가 출력이 안됐었다. 그리고 변수선언을 try문 밖에다가 쓰지 않고 안에다가 써야하는지 고민하던 중 시간이 다 되었다. 강사님께 굳이 밖에다가 정의하는 이유가 혹시 이 후에 또 사용할 참조변수라서 쓰는거냐고 물어보니 자동으로 이클립스가 만든게 밖으로 빼는거고 그게 맞다고 하셨다. 츄라이문 안에 넣어도 된다고 하셨다.

 

 

 

// 오늘의 단축키

 

 

//오늘의 질문 

Q. AgeException 클래스에서 오버로딩시 문자열로 매개변수 선언했는데 age가 들어갔는데 왜 오류가 안뜨나요?

A. +로 문자열과 연결되면 문자열로 인식함

 

//오늘의 숙제

복습....!