예외처리(Exception handling) - try~catch finally, throws, throw

기록하는 용도·2022년 8월 17일
0

Exception Handling(예외처리)

프로그램 수행 중 Exception 발생 시 예외 대처 흐름 or 대처 방안 or 핸들러 로직을 실행하고 프로그램을 정상 실행하는 것을 말한다.
(JVM이 프로그램 실행 중 Exception을 발생시키고 별도의 Handling이 없으면 기본적으로 예외 발생 즉시 종료가 된다.)

예) 실세계 배아프면(예외:Exception) 병원가서 주사 또는 약 먹고(Handling:처리) 공부 계속한다(정상 실행)

프로그램 실행 중 어떤 원인에 의해 프로그램이 비정상적으로 종료되는 경우가 있다. 이럴 때 프로그램 에러 또는 오류라고 한다.

이를 컴파일 에러(compile-error)런타임 에러(runtime-error)로 나눌 수 있다.

컴파일에러

컴파일 할 때 발생하는 에러로 문법 오류가 있는 등 에러 메시지로 비교적 쉽게 해결할 수 있는 에러를 말한다.

런타임에러

프로그램 실행 시 발생하는 에러로 코드의 문법 자체는 문제가 없지만 실행 했을때 잠재적인 오류가 발생하는 에러이다.

이러한 런타임 에러를 방지하기 위해 대비가 필요하다.
이처럼 runtime(실행)시 발생할 수 있는 프로그램 오류를 에러(error)예외(exception)으로 구분할 수 있다.

에러는 시스템 자원이 부족하여 오류가 발생하는 등의 비정상적인 종료를 말하고 예외는 수습될 수 있는 경우를 말한다.



예외 클래스의 계층구조

모든 예외의 최고 조상은 Exception 클래스이다.

최고 조상인 Exception 클래스를 제외하고 크게는 두가지로 분류할 수 있다.
1. RuntimeException
2. RuntimeException을 제외한 자손 클래스들(IOException 클래스 등)

RuntimeException 클래스들은 익숙한 예외들을 포함한다.

  • IndexOutOfBoundsException (배열의 범위를 벗어남)
  • NullPointerException (값이 null인 참조변수의 멤버를 호출)

RuntimeException을 제외한 자손 클래스들(IOException 클래스 등)

  • IOException
  • FileNotFoundException



예외처리하기

이러한 예외를 미리 대비하여 처리하는것을 예외처리(Exception handling)라고한다.

예외를 처리하기 위해서는 try ~ catch문을 사용할 수 있다.

예제1-1)

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

name이라는 변수에 "아이유"를 담았지만 그 후로 null값을 할당했다.

예제1-1 결과)

NullPointerException이 발생한 것을 할 수 있다.
이를 예외가 발생했다고 말하고 별도의 처리(핸들러)가 없기때문에 즉시 메시지 출력 후 비정상종료 된다.

예제1-2)

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() + "자로 이름이 구성됨"); //실행 내용
		}catch(NullPointerException ne) { //catch : 예외 처리 Exception Handling
			System.out.println(ne.getMessage() + " : 예외처리 -> 이름 정보가 존재하지않아 길이를 구할 수 없습니다."); 
//			ne.printStackTrace(); -> Exception 발생 경로를 출력
		}
		System.out.println("프로그램 정상 수행");
	}
}

예제1-1과 다르게 별도의 처리를 통해 프로그램의 비정상적인 종료를 막도록 했다.
일단 예외가 발생할 것 같은 문장을 try 문 안에 넣고 예외가 발생하게 된다면 catch문으로 처리할 수 있도록 한 구조이다.

예제1-2 결과)


프로그램이 비정상적인 종료를 막았고 예외 처리가 된 것을 알 수 있다.
그리고 마지막 "프로그램 정상 수행" 문장까지 출력했다.
예외처리가 되면 다음 코드들은 순차적으로 수행 됨을 알 수 있다.

예제2-1)

import java.util.ArrayList;

public class TestException3 {
	public static void main(String[] args) {
		// TestException1 처럼 Exception을 고의로 발생시켜 비정상 종료되는 예제
		// TestException4에서 Exception Handling(예외처리) 
		ArrayList<String> list = new ArrayList<String>();
		list.add("짜장면");
		list.add("탕수육");
		System.out.println(list.get(2));
		System.out.println("정상 수행");
		
	}
}

다른 예외 처리를 살펴보기 위한 예제이다.
예제 1-1 처럼 별도의 예외 처리 없이 의도적으로 예외를 발생시키는 예제이다.

예제2-1 결과)


프로그램이 비정상적으로 종료됨을 알 수 있다.
IndexOutOfBoundsException에 해당하는 예제이다.
Index는 2를 주었지만, 선언한 list는 size가 2이기때문에 결과를 불러 올 수 없어 예외가 발생했다.

예제2-2)

import java.util.ArrayList;

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

예제 2-1을 예외처리 하는 예제이다.
마찬가지로 try문 안에 예외가 발생할 것 같은 문장을 넣고 처리가 가능한 catch문을 찾아 수행되는것을 확인하는 예제이다.

예제2-2 결과)


코드를 순차적으로 확인해보면 name에 null값을 할당하고, try문에서 출력문 첫줄부터 예외가 발생함을 예제2-1에서 확인했다.
이 때 해당하는 catch문을 찾기위해 try문을 바로 빠져나온다.
첫번째 catch문인 IndexOutOfBoundsException 예외에 해당하지 않기 때문에 모든 예외의 최고 조상인 Exception catch 블럭이 실행된다.
c가 순차적으로 출력되고 printStackTrace() 를 통해 예외발생시 예외 메시지와 그 정보를 화면에 출력함을 볼 수 있다.
예외 처리가 되었기때문에 try ~ catch문을 빠져나와 나머지 두개의 출력문이 실행됨을 알 수 있다.

예제3) - finally블럭

finally 블럭은 예외 처리에 실패해도 이 구문은 언제나 실행한다.

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){ //NumberFormatException 발생하는데 다른 예외를 고의로 처리해본다. -> 처리 안됨
			System.out.println("null 이므로 출력 불가");
		}
		finally { //현 위치에 finally블록을 쓰는 것과 안쓰는 것과의 차이
					 // Exception 발생시 Exception Handling (예외처리)에 실패하더라도 finally 구문은 실행된다.
			System.out.println("finally 영역 : 예외 발생 및 처리 여부와 관계 없이 항상 실행");
		}
		
		System.out.println("정상 수행");
	}
	

예제3-1 결과)

try 문을 통해 문자열 데이터 타입인 age를 정수형으로 바꾸는것이 불가능하기때문에 예외가 발생한다. 이때

System.out.println(iage + 1);

문을 실행하지않고 예외 처리하기위해 해당하는 catch문을 찾으러 빠져나온다.
이에 해당하는 예외 처리 블럭이 없지만, finally 블럭은 언제나 실행되기때문에 실행된것을 알 수 있다.
예외처리는 안되었기때문에

System.out.println("정상 수행");

는 실행되지않는다.

예제4) - finally 블럭

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)); //IndextOutOfBoundsIndex 발생
			System.out.println("B");
		}
		catch(NullPointerException ne){
			System.out.println("C");
		}
		finally {
			System.out.println("D");
		}
		System.out.println("E");
		
	}

예제4 결과)


예외가 발생한

System.out.println(list.get(0)); 

부분에서 바로 빠져나와 해당하는 catch 블럭을 찾아간다.
해당하는 catch블럭이 없어 상관없이 실행되는 finally 블럭을 실행하고, 예외 처리 되지않았기때문에

System.out.println("E");

문은 실행되지않는것을 알 수 있다.

예제5) finally 블럭

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)); //IndexOutOfBoundsIndex 발생
			System.out.println("B");
		}
		catch(RuntimeException re){ //IndextOutOfBoundsException의 부모 Exception
			System.out.println("C");
		}
		finally {
			System.out.println("D");
		}
		System.out.println("E");
		
	}
}

예제5 결과)

try문에서

System.out.println(list.get(0));

에서 예외가 발생해 바로 빠져나온다.

catch(RuntimeException re)

예외 클래스의 계층구조를 보면 알 수있는 것처럼 RuntimeException은 IndexOutOfBoundsException의 부모 Exception이다.
여기에 바로 해당되기때문에 C를 출력하고 순차적으로 아래 코드도 실행된다.



키워드 throws

Exception 을 호출한 측으로 던질 수 있다는 의미 (메소드 선언시 사용)

예제1) - throws

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

public class TestThrows1 {
	public static void main(String[] args) {
		try {
			FileReader fr = new FileReader("C:\\user\\a.txt");
			//file 실제로 존재하면 정상적으로 입력 작업 처리
			//file이 존재하지않으면 FileNotFoundException 
			//java se api에서 제공하는 File 발생되어 throw 되고 catch이 실행된다.
			System.out.println(fr + " file reader");
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} //checked Exception 컴파일시에 체크
		System.out.println("정상 수행");
	}
}


file이 실제로 존재할 때 주소값 + file reader가 출력되었다.


파일이 존재하지않기때문에 객체 생성시 예외가 발생했을 것이다.
해당하는 FileNotFoundException catch블럭 내에서 예외메시지를 출력했고 예외처리 되었기때문에 "정상 수행" 도 출력됐다.

예외를 처리하는 방법 중 메소드에 선언하는 방법이 있는데 throws이다.

void method() throws Exception {
}

모든 예외의 최고 조상인 Exception 클래스를 메소드에 예외 선언했다.
모든 종류의 예외가 발생할 가능성이 있다는 뜻이다.

예제1-2) - throws

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

public class TestThrows2 {
	public static void main(String[] args) throws FileNotFoundException {
		// main 에서 throws 한다는 것은 실행의 주체인 jvm에게 해당 Exception 을 전달하겠다는 의미
		// 예외처리를 하지 않겠다는 의미 , 단순히 CheckedException에 의한 compile error 를 피하는 용도 
		FileReader fr = new FileReader("C:\\user\\a.txt");
		System.out.println(fr + " file reader");
		System.out.println("정상 수행");
	}
}

a2.txt는 존재하지않는 파일이다.
FileNotFoundException를 통해 자신이 예외를 처리하는 것이아니라 자신을 호출한 메소드에게 예외를 전달해서 예외처리를 떠맡기는 것이다.

public static void main(String[] args) throws FileNotFoundException

만약 예외를 떠맡기는 코드가 없다면(throws FileNotFoundException)

Unhandled exception type FileNotFoundException

라는 메시지가 나타난다. 이는 이 코드에 대한 예외처리가 필요하다는 뜻이다.


위의 내용은 생성자 FileReader의 인자 fileName의 값에 해당하는 파일이 디렉토리이거나 어떤 이유로 사용할 수 없다면 FileNotFoundException을 발생시킨다는 의미다.



이것은 FileReader의 생성자가 동작할 때 파일을 열 수 없는 경우가 생길 수 있고, 이런 경우 생성자 FileReader에서는 이 문제를 처리할 수 없기 때문에 이에 대한 처리를 생성자의 사용자에게 위임하겠다는 의미다.
(출처:https://opentutorials.org/module/516/6227)

예제2) - throws


import java.io.FileNotFoundException;
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);
		    System.out.println("A");
		}finally {
			if(fr!=null)
			fr.close();
			System.out.println("D");
		}
		return fileName+"에 기록된 정보";//정상 흐름일때 동작
	}

}



키워드 throw

Exception을 고의로 발생

고의로 예외를 발생시킬 수 있다. 키워드 throw를 이용해서 발생시킨다.

키워드 throw를 이용해서 발생시키는 과정은 다음과 같다.
발생시키려는 예외 클래스의 객체를 만들고, 키워드 throw를 이용해 예외를 발생시킨다.

throw - 예제1)

import java.io.IOException;

// throws : Exception 발생시 호출한 측으로 전파 또는 위임 
// throw : Exception 을 발생시킬 때 사용 
class Service{
	public void test1(boolean flag) {
		if(flag) // throw : 예외를 발생시킬 때 사용하는 키워드 
			throw new NullPointerException();// Runtime 계열 Exception( UnChecked Exception)은 별도로 throws 명시 필요없음 
		System.out.println("throw test");		
	}
	
	public void test2(boolean flag) throws IOException {
		if(flag) // throw : 예외를 발생시킬 때 사용하는 키워드 
			throw new IOException();// Checked Exception( Runtime 계열을 제외한 모든 Exception ) 은 별도로 throws or try catch 처리 필요 
		System.out.println("throw test");
	}
}
public class TestThrow1 {
	public static void main(String[] args) {
		Service service=new Service();
		try {
		 service.test1(true);
		}catch (NullPointerException e) {
			System.out.println("test1 예외발생에 대해 처리");
		}
		try {
			service.test2(true);
		} catch (IOException e) {
			System.out.println("test2 예외발생에 대해 처리");
		}
		System.out.println("정상 수행");
	}
}
  1. main
public static void main(String[] args) {
	Service service=new Service();
	try {
		 service.test1(true);
	}catch (NullPointerException e) {
		System.out.println("test1 예외발생에 대해 처리");
	}

service의 test1 메소드에 true값을 인자를 주며 호출했다.

class Service{
	public void test1(boolean flag) {
		if(flag) // throw : 예외를 발생시킬 때 사용하는 키워드 
			throw new NullPointerException();// Runtime 계열 Exception( UnChecked Exception)은 별도로 throws 명시 필요없음 
		System.out.println("throw test");		
	}

throw 키워드를 통해서 객체를 만듦과 동시에 예외를 발생시켰다.
위 코드는

NullPointerException n = new NullPointerException();
thrwo n;

과 같다.

그럼 다시 main 으로 돌아와서

}catch (NullPointerException e) {
	System.out.println("test1 예외발생에 대해 처리");
}

발생한 예외와 catch 블럭이 일치해 그 내부가 실행된다.

출력 결과는 다음과 같다.

0개의 댓글