이것이 자바다 - Part 11

mj·2023년 1월 19일
0
post-thumbnail

Part 11 예외 처리

예외와 예외 클래스

컴퓨터 하드웨어의 고장으로 인해 응용프로그램 실행 오류가 발생하는 것을 자바에서는 에러라고 한다.
프로그램을 아무리 견고하게 만들어도 개발자는 이런 에러에 대처할 방법이 전혀 없다.

자바에서는 에러이외에 예외라고 부르는 오류가 있다.
예뢰란 잘못된 사용 또는 코딩이로 인한 오류를 말한다.
예외가 발생되면 프로그램은 곧바로 종료된다는 점에서 에러와 동일하지만, 예외 처리를 통해 계속 실행 상태를 유지할 수 있다.

예외 종류

  • 일반 예외 : 컴파일러가 예외 처리 코드 여부를 검사하는 예외를 말한다.
  • 실행 예외 : 컴파일러가 예외 처리 코드 여부를 검사하지 않는 예외를 말한다.

자바는 예외가 발생하면 예외 클래스로부터 객체를 생성한다.
이 객체는 예외 처리 시 사용된다.

자바의 모든 에러와 예외 클래스는 Throwable을 상속받아 만들어지고, 추가적으로 예외 클래스는 java.lang.Exception 클래스를 상속받는다.

실행 예외는 RuntimeException 과 그 자식 클래스에 해당한다.
자바는 자주 사용되는 예외 클래스를 표준 라이브러리로 제공한다.

예외 처리 코드

예외가 발생했을 때 프로그램의 갑작스러운 종료를 막고 정상 실행을 유지할 수 있도록 처리하는 코드를 예외 처리 코드라고 한다.
예외 처리 코드는 try-catch-finally 블록으로 구성된다.

try-catch-finally 블록은 생성자 내부와 메소드 내부에서 작성된다.

try {
	//예외 발생 가능 코드
} catch(예외클래스 e) {
	//예외 처리
} finally {
	// 항상 실행
}

try 블록에서 작성한 코드가 예외 없이 정상 실행되면 catch 블록은 실행되지 않고 finally 블록이 실행된다.
그러나 try 블록에서 예외가 발생하면 catch 블록이 실행되고, 연이어 finally 블록이 실행된다.

finally 블록

  • 예외 발생 여부와 상관없이 항상 실행된다.
  • try 블록과 catch 블록에서 return 문을 사용하더라도 항상 실행된다.
  • 옵션으로 생략 가능하다.

예외 내용을 출력하는 방법

try {
	//예외 발생
} catch(Exception e) {
	...
}
  • e.getMessage() : 예외가 발생한 이유 리턴
  • e.toString() : 예외가 발생한 이유 + 예외의 종류 리턴
  • e.printStackTrace() : 예외가 어디서 발생했는지 추적한 내용까지 출력

예외 종류에 따른 처리

try 블록에는 다양한 종류의 예외가 발생할 수 있다.

이 경우, 다중 catch 를 사용하면 발생하는 예외에 따라 예외 처리 코드를 다르게 작성할 수 있다.
catch 블록의 예외 클래스는 try 블록에서 발생된 예외의 종류를 말하는데, 해당 타입의 예외가 발생하면 catch 블록이 선택되어 실행된다.

try {
	//ArrayIndexOutOfBoundException 발생
    //NumberFormatException 발생
} catch (ArrayIndexOutOfBoundException e) {
	...
} catch (NumberFormatException e) {
	...
}

catch 블록이 여러 개라 할지라도 catch 블록은 단 하나만 실행된다. 그 이유는 try 블록에서 동시 다발적으로 예외가 발생하지 않으며, 하나의 예외가 발생하면 즉시 실행을 멈투고 해당 catch 블록으로 이동하기 때문이다.

처리해야 할 예외 클래스들이 상속된계에 있을 때는 하위 클래스 catch 블록을 먼저 작성하고 상위 클래스 catch 블록을 나중에 작성해야 한다.
예외가 발생하면 catch 블록은 위에서부터 차례대로 검사 대상이 되는데, 하위 예외도 상위 클래스 타입이므로 상위 클래스 catch 블록이 먼저 검사 대상이 되면 안된다.

try {
	//ArrayIndexOutOfBoundException 발생
    //NumberFormatException 발생
} catch (Exception e) {
	...
} catch (NumberFormatException e) {
	//해당 catch 블록은 실행되지 않음
}

두 개 이상의 예외를 하나의 catch 블록으로 동일하게 예외 처리하고 싶을 때가 있다.
이 경우에는 catch 블록이 예외 클래스를 기호 | 로 연결하면 된다.

try {
	//예외 발생
} catch (NullPointerException | NumberFormatException e) {
	// 예외 처리
}

리소스 자동 닫기

리소스란 데이터를 제공하는 객체를 말한다.
리소스는 사용하기 위해 열러야(open)하며, 사용이 끝난 다음에는 닫아야(close) 한다. 예를 들어 파일 내용을 읽기 위해서는 파일을 열어야 하며, 다 읽고 난 후에는 파일을 닫아야 다른 프로그램에서 사용할 수 있다.

리소스를 사용하다가 예외가 발생될 경우에도 안전하게 닫는 것이 중요하다.
그렇지 않으면 리소스가 불안정한 상태로 남아있게 된다.

다음 코드는 file.txt 파일의 내용을 읽기 위해 FileInputStream 리소스를 사용하는데, 예외 발생 여부와 상관없이 finally 블록에서 안전하게 close 한다.

FileInputStream fis = null;
try {
	fis = new FileInputStream("file.txt");
    ...
} catch (IOException e) {
	...
} finally {
	fis.close();
}

try-with-resources 블록을 사용하면 예외 발생 여부와 상관없이 리소스를 자동으로 닫아준다.

try 괄호에 리소스를 여는 코드를 작성하면 try 블록이 정상적으로 실행을 완료했거나 도중에 예외가 발생하면 자동으로 리소스의 close() 메소드가 호출된다.

try (FileInputStream fis = new FileInputStream("file.txt")) {
	...
} catch(IOException e) {
	...
}

try-with-resources 블록을 사용하기 위해서는 조건이 하나 있다.
리소스는 java.lang.AutoClosable 인터페이스를 구현해서 AutoCloseable 인터페이스의 close() 메소드를 재정의해야 한다.
예를 들어 FileInputStream 은 다음과 같이 AutoCloseable 인터페이스를 구현하고 있다.

public class FileInputStream implements AutoCloseable {
	...
    @Override
    public void close() throws Exception { ... }
}

복수 개의 리소스를 사용해야 한다면 다음과 같이 try() 괄호 안에 세미콜론(;)으로 구분해서 리소스를 여는 코드를 작성하면 된다.

try (
	FileInputStream fis1 = new FileInputStream("file1.txt");
    FileInputStream fis2 = new FileInputStream("file2.txt")
) {
	...
} catch (Exception e) {
	...
}

Java9 이후부터는 외부 리소스 변수를 사용할 수 있다.

FileInputStream fis1 = new FileInputStream("file1.txt");
FileInputStream fis2 = new FileInputStream("file2.txt")
try (fis1; fis2 ) {
	...
} catch (Exception e) {
	...
}

예외 떠넘기기

메소드 내부에서 예외가 발생할 때 try-catch 블록으로 예외를 처리하는 것이 기본이지만, 메소드를 호출한 곳으로 예외를 떠넘길 수도 있다. 이 때 사용하는 키워드가 throws 이다.

throws 는 메소드 선언부 끝에 작성하는데, 떠넘길 예외 클래스를 쉼표로 구분해서 나열해주면 된다.

리턴타입 메소드명(매개변수, ...) throws 예외클래스1, 예외클래스2, ... {
}

throws 키워드가 붙어 있는 메소드에서 해당 예외를 처리하지 않고 떠넘겼기 때문에 이 메소드를 호출하는 곳에서 예외를 받아 처리해야 한다.

public void method1() {
	try {
    	method2();
    } catch (ClassNotFoundException e) {
    	System.out.println("예외 처리 : " + e.getMessage());
    }
}

public void method2() throws ClassNotFoundException {
	Class.forName("java.lang.String2");
}   

나열해야 할 예외 클래스가 많을 경우에는 throws Exception 또는 thorws Throwable 만으로 모든 예외를 간단히 떠넘길 수 있다.

리턴타입 메소드명(매개변수, ...) throws Exception {
}
public static void main(String[] args) throws Exception {
	...
}

사용자 정의 예외

은행의 뱅킹 프로그램에서 잔고보다 더 많은 출금 요청이 들어온 경우에는 잔고 부족 예외를 발생시킬 필요가 있다.
그러나 잔고 부족 예외는 표준 라이브러리에는 존해하지 않기 때문에 직접 예외 클래스를 정의해서 사용해야 한다.
이것을 사용자 정의 예외라고 한다.

사용자 정의 예외

사용자 정의 예외는 컴파일러가 체크하는 일반 예외로 선언할 수도 있고, 컴파일러가 체크하지 않는 실행 예외로 선언할 수도 있다.
통상적으로 일반 예외는 Exception 의 자식 클래스로 선언하고, 실행 예외는 RuntimeException 의 자식 클래스로 선언한다.

public class XXXException extends [ Exception | RuntimeException ] {
	public XXXException() {
    }
    
    public XXXException(String message) {
    	super(message);
    }
}

사용자 정의 예외 클래스에는 기본 생성자와 예외 메시지를 입력받는 생성자를 선언해준다.
예외 메시지는 부모 생성자 매개값으로 넘겨주는데, 그 이유는 예외 객체의 공통 메소드인 getMessage() 의 리턴값으로 사용하기 때문이다.

예외 발생 시키기

자바에서 제공하는 표준 예외뿐만 아니라 사용자 정의 예외를 직접 코드에서 발생시키려면 throw 키워드와 함께 예외 객체를 제공하면 된다.

throw new Exception();
throw new RuntimeException();
throw new InsufficientException();

throw 된 예외는 직접 try-catch 블록으로 예외를 처리할 수도 있지만, 대부분은 메소드를 호출한 곳에서 예외를 처리하도록 throws 키워드로 예외를 떠넘긴다.

void method() {
	try {
    	...
        throw new Exception("예외 메세지");
        ...
    } catch (Exception e) {
    	String message = e.getMessage();
    }
}
void method() throws Exception {
	...
    throw new Exception("예외메세지");
    ...
 }

문제

  1. 예외에 대한 설명 중 틀린 것은 무엇입니까?
    ➊ 예외는 사용자의 잘못된 조작, 개발자의 잘못된 코딩으로 인한 프로그램 오류를 말한다.
    ➋ RuntimeException의 하위 예외는 컴파일러가 예외 처리 코드를 체크하지 않는다.
    ➌ 예외는 try-catch 블록을 사용해서 처리된다.
    ➍ 자바 표준 예외만 프로그램에서 처리할 수 있다.
  • 답 : ➍
  1. try-catch-finally 블록에 대한 설명 중 틀린 것은 무엇입니까?
    ➊ try { } 블록에는 예외가 발생할 수 있는 코드를 작성한다.
    ➋ catch { } 블록은 try { } 블록에서 발생한 예외를 처리하는 블록이다.
    ➌ try { } 블록에서 return 문을 사용하면 finally { } 블록은 실행되지 않는다.
    ➍ catch { } 블록은 예외의 종류별로 여러 개를 작성할 수 있다.
  • 답 : ➌
  1. throws에 대한 설명으로 틀린 것은 무엇입니까?
    ➊ 생성자나 메소드의 선언 끝 부분에 사용되어 내부에서 발생된 예외를 떠넘긴다.
    ➋ throws 뒤에는 떠넘겨야 할 예외를 쉼표(,)로 구분해서 기술한다.
    ➌ 모든 예외를 떠넘기기 위해 간단하게 throws Exception으로 작성할 수 있다.
    ➍ 새로운 예외를 발생시키기 위해 사용된다.
  • 답 : ➍
  1. throw에 대한 설명으로 틀린 것은 무엇입니까?
    ➊ 예외를 최초로 발생시키는 코드이다.
    ➋ 예외를 호출한 곳으로 떠넘기기 위해 메소드 선언부에 작성된다.
    ➌ throw로 발생된 예외는 일반적으로 생성자나 메소드 선언부에 throws로 떠넘겨진다.
    ➍ throw 키워드 뒤에는 예외 객체 생성 코드가 온다.
  • 답 : ➋
  1. 메소드가 다음과 같이 선언되어 있습니다. 잘못된 예외 처리를 선택하세요.
public void method1() throws NumberFormatException, ClassNotFoundException {}

➊ try { method1(); } catch (Exception e) { }
➋ void method2() throws Exception { method1(); }
➌ try { method1(); }
catch (Exception e) { }
catch (ClassNotFoundException e) { }
➍ try { method1(); }
catch (ClassNotFoundException e) { }
catch (NumberFormatException e) { }

  • 답 : ➌
  1. 다음 코드가 실행되었을 때 출력 결과를 작성해보세요.
	String[] strArray = { "10", "2a" };
	int value = 0;
	for(int i=0; i<= 2; i++) {
		try {
 			value = Integer.parseInt(strArray[i]);
		} catch(ArrayIndexOutOfBoundsException e) {
 			System.out.println("인덱스를 초과했음");
		} catch(NumberFormatException e) {
 			System.out.println("숫자로 변환할 수 없음");
		} finally {
 			System.out.println(value);
		}
	}
  • 답 :
10
숫자로 변환할 수 없음
10
인덱스를 초과했음
10
  1. login() 메소드에서 존재하지 않는 ID를 입력하면 NotExistIDException을 발생시키고, 잘못된
    패스워드를 입력하면 WrongPasswordException을 발생시키려고 합니다. 다음 LoginExample
    의 실행 결과를 보고 빈칸을 채워보세요.
public class NotExistIDException extends Exception {
	public NotExistIDException() {}
	public NotExistIDException(String message) {
		// 빈칸 작성
		// super(message);
	}
}
public class WrongPasswordException extends Exception {
	public WrongPasswordException() {}
	public WrongPasswordException(String message) {
		//빈칸 작성
 		//super(message);
	}
}
public class LoginExample {
	public static void main(String[] args) {
 		try {
 			login("white", "12345");
 		} catch(Exception e) {
 			System.out.println(e.getMessage());
 		}
 		try {
 			login("blue", "54321");
 		} catch(Exception e) {
 			System.out.println(e.getMessage());
 		}
	}
	public static void login(String id, String password) // 빈칸 작성	// throws NotExistIDException, WrongPasswordException 
    {
 		//id가 blue가 아니라면 NotExistIDException을 발생시킴
 		if(!id.equals("blue")) {
			//빈칸 작성
 			// throw new NotExistIDException("아이디가 존재하지 않습니다.");
 		}//password가 12345가 아니라면 WrongPasswordException을 발생시킴
 		if(!password.equals("12345")) {
			//빈칸 작성
 			// throw new WrongPasswordException("패스워드가 틀립니다.");
 		}
	}
}
//실행 결과
아이디가 존재하지 않습니다.
//실행 결과
패스워드가 틀립니다.
  • 답 :
  1. FileWriter는 파일을 열고 데이터를 저장하는 클래스입니다. 예외 발생 여부와 상관 없이 마지막
    에는 close() 메소드를 실행해서 파일을 닫아주려고 합니다. 왼쪽 코드는 try-catch-finally를 이
    용해서 작성한 코드로, 리소스 자동 닫기를 이용하도록 수정하고 싶습니다. 수정한 코드를 오른쪽에
    작성해보세요.

  • 답 :
try( FileWriter fw = new FileWriter("file.txt") ) {
	fw.write("Java");
} catch (IOException e) {
	e.printStackTrace();
}
profile
사는게 쉽지가 않네요

0개의 댓글