1. 예외처리(Exception Handling)
Def) 예외: 컴파일도 되고 실행도 되지만 의도와 다른게 동작하는 것
1.1 프로그램 오류
- 컴파일 에러: 컴파일 시에 발생하는 에러 ex) system.out.println(); => 오류
- 런타임에러: 실행 시에 발생하는 에러
- 논리적 에러: 실행되지만, 의도와 다르게 동작하는 것
- 에러와 예외 차이
-> 에러: 코드에 의해 수습될 수 없는 심각한 오류
-> 예외: 코드로 수습할 정도의 미약한 오류(실행 잘 되지만 의도와 다르게 동작)
- 예외처리의 정의, 목적
Def) 프로그램 실행 시 발생할 수 있는 예외의 발생에 대비한 코드를 작성하는 것
- 목적: 비정상 종료를 막고, 정상적 실행상태를 유지한다.
1.2 예외 클래스의 계층구도
- 모든 예외는 Exception(모든 예외의 최고 조상)의 자손 클래스
- checked예외: 컴파일러가 예외처리 여부를 체크(예외 처리(try-catch) 필수)
-> Exception클래스와 그 자손들, RuntimeException는 제외)
- unchecked예외: 컴파일러가 예외처리 여부를 체크하지 않는다 (예외 처리 선택) - (RuntimeException와 자손들)
- RuntimeException클래스들은 주로 프로그래머의 실수에 의해 발생될 수 있는 예외들이다.
- Exception클래스들은 주로 외부의 영향으로 발생할 수 있는 것들이다.
-> 주로 프로그램 사용자의 동작에 의해 발생한다.
1.3 예외처리하기 try - catch문
- 예외발생하면 이를 처리할 catch블럭을 찾아 내려간다.
- 일치하는 catch블럭을 찾지 못하면 예외는 처리되지 못한다.
- Exception 선언된 catch블럭은 맨 마지막 블럭.
<hide/>
try{
// 예외가 발생할 가능성이 있는 문장들을 넣는다.
}catch (Exception1 e1) {
// Exception1이 발생했을 때 처리를 위한 문장을 넣는다.
}catch (Exception2 e2) {
// Exception2이 발생했을 때 처리를 위한 문장을 넣는다.
}catch (Exception3 e3) {
// Exception3이 발생했을 때 처리를 위한 문장을 넣는다.
}
Note) 하나의 try블럭 다음에는 여러 종류의 예외를 처리할 수 있도록 하나 이상의 catch블럭이 올 수 있다.
- 이 중에서 발생한 예외와 일치하는 단 한 개의 catch블럭만 수행된다.
- 일치하는 예외가 없으면 처리 되지 않는다.
- catch블럭 내에 또 다른 try-catch문이 포함된 경우에는 같은 이름의 참조변수를 사용할 수 없다.
1.5 예외발생과 catch블럭
- printStackTrace() : 예외발생 당시의 호출 스택(Call Stack)에 있었던 메서드의 정보와 예외 메시지를 출력
- getMessage() : 발생한 예외클래스의 인스턴스에 저장된 메시지를 얻을 수 있다.
- 멀티 catch블럭: 내용이 같은 catch블럭을 하나로 합친 것
1.6 예외 발생시키기
1) 먼저, 연산자 new를 이용해서 발생시키려는 예외 클래스의 객체를 만든다
2) 키워드 throw를 이용해서 예외를 발생시킨다.
Ex)
<hide/>
package javaStudy;
public class ExceptionEx9 {
public static void main(String[] args) {
try {
Exception e = new Exception("고의로 발생시켰음.");
throw e; //예외를 발생시킴
// throw new Exception("고의로 발생시켰음.");
}catch(Exception e) {
System.out.println("에러메시지 : " + e.getMessage());
e.printStackTrace();
}
System.out.println("프로그램이 정상 종료되었음");
}
}
Note)
- 예외 발생시키는 두 줄을 간략히, throw new exception("고의로 발생시켰음."); 으로 나타낼 수 있다.
1.7 메서드에 예외 선언하기
- 메서드가 호출시 발생가능한 예외를 호출하는 쪽에 알리는 것
<hide/>
void method() throws Exception1, Exception2, ... , ExceptionN{
// 메서드의 내용
}
- 만일 위에 Exception만 선언하면 예외뿐만 아니라 자손타입의 예외까지도 발생할 가능성이 있다는 것이다.
- 오버라이딩 할 때는 선언된 예외의 개수가 아니라 상속관계까지 고려해야한다.
Ex)
<hide/>
package javaStudy;
public class ExceptionEx12 {
public static void main(String[] args) throws Exception {
method1(); // 같은 클래스내의 static멤버이므로 객체생성없이 직접 호출 가능.
}
static void method1() throws Exception {
method2();
}
static void method2() throws Exception{
throw new Exception(); // 예외 강제로 발생
}
}
Note) 실행 결과
- 프로그램의 실행도중 java.lang.Exception이 발생하여 비정상적으로 종료했다.
- 예외 발생했을 때의 호출 스택내용을 알 수 있다.
1) 예외가 발생했을 때, 모두 3개의 메서드가 호출 스택에 있었다.
2) 예외가 발생한 곳은 제일 윗줄에 있는 method2()
3) main메서드가 method1() 호출, 그리고 method1()은 method2()를 호출했다.
- method2에서 예외가 (강제로) 발생했으나 예외처리를 해주지 않았기 때문에 메서드 종료하고
- 자신을 호출한 method1()에게 예외를 넘겨준다.
- 그러나 main메서드에서조차 예외처리를 해주지 않아서 main이 종료되어
- 프로그램이 예외로 인해 비정상적으로 종료된다.
- 따라서 어느 한 곳에서 반드시 try-catch문으로 예외처리해야한다.
Ex 8-15)
package javaStudy;
import java.io.*;
public class ExceptionEx15 {
public static void main(String[] args) {
// command line에서 입력받은 값을 이름으로 갖는 파일을 생성한다.
File f = createFile(args[0]);
System.out.println(f.getName() + " 파일이 성공적으로 생성되었습니다." );
}
static File createFile(String fileName) {
try {
if( fileName == null || fileName.equals("") )
throw new Exception("파일이름이 유효하지 않습니다.");
}catch (Exception e){
// fileName이 부적절한 경우, 파일이름을 '제목없음.txt'로 한다.
fileName = "제목없음.txt";
}finally {
File f = new File(fileName); // File클래스의 객체를 만든다.
createNewFile(f); // 생성된 객체를 이용해서 파일을 생성한다.
return f;
}
}
static void createNewFile(File f) {
try {
f.createNewFile(); // 파일을 생성한다.
}catch(Exception e) {}
}
}
Note)
- 이 예제는 예외가 발생한 메서드에서 직접 예외를 처리하도록 되어 있다.
- createFile메서드를 호출한 main메서드에서는 예외가 발생한 사실을 알지 못한다.
1.8 finally블럭
- 예외발생 여부와 관계없이 수행되어야 하는 코드
<hide/>
try{
// 예외가 발생할 가능성이 있는 문장들을 넣는다.
}catch (Exception e) {
// 예외처리를 위한 문장을 넣는다.
}finally{
// 예외 발생여부와 관계 없이 항상 수행할 문장
// finally블럭은 try-catch문의 맨 마지막에 위치해야한다.
}
1.9 자동 자원 반환 - try - with - resources 문
- try-catch문의 변형이다.
- 입출력에 사용되는 클래스 중에서는 사용 후에 꼭 닫아줘야하는 것들이 있다.
- 그래야 사용했던 자원이 반환(resource)되기 때문이다.
Ex)
<hide/>
package javaStudy;
public class TryWithResourceEx {
public static void main(String[] args) {
try (CloseableResource cr = new CloseableResource () ){
cr.exceptionWork(false); // 에외가 발생하지 않늗다.
}catch(WorkException e) {
e.printStackTrace();
}catch(CloseException e) {
e.printStackTrace();
}
System.out.println();
try (CloseableResource cr = new CloseableResource () ){
cr.exceptionWork(true); // 에외가 발생하지 않늗다.
}catch(WorkException e) {
e.printStackTrace();
}catch(CloseException e) {
e.printStackTrace();
}
}
}
class CloseableResource implements AutoCloseable {
public void exceptionWork(boolean exception) throws WorkException{
System.out.println( "exceptionWork(" + exception + ")가 호출됨" );
if(exception)
throw new WorkException("WorkException발생!!!");
}
public void close() throws CloseException{
System.out.println("close()가 호출됨");
throw new CloseException("CloseException발생!!!");
}
}
class WorkException extends Exception{
WorkException(String msg){super(msg); }
}
class CloseException extends Exception{
CloseException(String msg){super(msg); }
}
Note) 실행 결과
- main메서드에 두 개의 try-catch문이 있는데 첫 번째 것은 close()에서만 예외를 발생시키고
- 두 번째 것은 exceptionWork()와 close()에서 모두 예외를 발생시킨다.
- 첫번째 것은 일반적인 예외가 발생했을 때와 같은 형태
- 두 번째는 출력형태가 다른.
- 두 예외가 동시에 발생할 수는 없다. 실제 발생한 예외를 'WorkException'이라고한다.
- CloseException은 억제된 예외이다.
-> void addSuppressed(Throwable exception) : 억제된 예외를 추가
-> Throwable [] getSuppressed() : 억제된 예외(배열)을 반환한다.
1.10 사용자 정의 예외 만들기
- 우리가 직접 예외를 정의할 수 있다.
- Exception과 RuntimeException 중에 조상을 선택한다.
<hide/>
class MyException estends Exception{
MyException(String msg){ //문자열을 매개변수로 받는 생성자
super(msg); // 조상인 Exception클래스의 생성자를 호출
}
}
Ex)
<hide/>
package javaStudy;
public class NewExceptioneTest {
public static void main(String[] args) {
try {
startInstall();
copyFiles();
}catch(SpaceException e) {
System.out.println("에러 메시지 : " + e.getMessage());
e.printStackTrace();
System.out.println("공간을 확보한 후에 다시 설치하시기를 바랍니다.");
}
catch(MemoryException me) {
System.out.println("에러 메시지 : " + me.getMessage());
me.printStackTrace();
System.gc(); // Garbage Collection을 수행하여 메모리를 늘려준다.
System.out.println("다시 설치를 시도하세요.");
}finally {
deleteTempFiles(); // 프로그램 설치를 사용된 임시파일들을 삭제한다.
}
}
static void startInstall() throws SpaceException, MemoryException{
if( !enoughSpace() ) // 충분한 설치 공간이 없으면
throw new SpaceException("설치할 공간이 부족합니다.");
if( !enoughMemory() ) // 충분한 설치 공간이 없으면
throw new MemoryException("메모리가 부족합니다.");
}
static void copyFiles() { /*파일들을 복사하는 코드 적는다.*/ }
static void deleteTempFiles() { /* 임시파일들을 삭제하는 코드 적는다.*/ }
static boolean enoughSpace() {
// 설치하는데 필요한 공간이 있는지 확인하는 코드를 적는다.
return false;
}
static boolean enoughMemory() {
// 설치하는데 필요한 공간이 있는지 확인하는 코드를 적는다.
return true;
}
}
class SpaceException extends Exception{
SpaceException(String msg){
super(msg);
}
}
class MemoryException extends Exception{
MemoryException(String msg){
super(msg);
}
}
Note) 실행 결과
- MemoryException, SpaceException 두 개의 사용자 정의 예외 클래스를 새로 만들었다.
- 두 예외는 startInstall()을 수행하는 동안 발생할 수 있으며
- enoughSpace()와 enoughMemory()의 실행 결과에 따라 예외의 종류가 달라지기도한다.
1.11 예외 되던지기(exception re-throwing)
- 예외를 처리한 후에 다시 예외를 발생시키는 것
- 호출한 메서드와 호출된 메서드 양쪽 모두에서 예외처리하는 것
Ex)
<hide/>
package javaStudy;
public class ExceptionEx17 {
public static void main(String[] args) {
try {
method1();
}catch(Exception e) {
System.out.println("main메서드에서 예외가 처리됐습니다.");
}
}
static void method1() throws Exception{
try {
throw new Exception();
}catch(Exception e) {
System.out.println("method1메서드에서 예외가 처리되었습니다.");
throw e; // 다시 예외를 발생시킨다.
}
}
}
Note) 실행 결과
- method1()과 main매서드 양쪽의 catch블럭이 모두 수행되었다.
- method1()의 catch블럭에서 예외를 처리하고도 throw문을 통해 다시 예외발생시켰다
- 그 예외를 main메서드에서 한 번 더 처리했다.
1.12 연결된 예외(chained exception)
- 한 예외가 다른 예외를 발생시킬 수 있다.
- 예외 A가 예외B를 발생시키면 A는 B의 원인예외(cause exception)
<hide/>
Throwable initCause(Throwable cause) : 지정한 예외를 원인 예외로 등록
Throwable getCause() : 원인 예외를 반환
- initCause()는 throwable 클래스에 정의 되어 있어서 모든 예외에서 사용 가능.
- 연결된 예외를 사용하는 이유
1) 여러 예외를 하나로 묶어 다루기 위해
2) checked예외를 unchecked예외로 변경하려할 때 (예외처리가 선택적인 사항이 된다.)
3) 예외의 상속 관계를 변경하지 않아도 된다.
Ex)
<hide/>
package javaStudy;
public class ChainedExceptionEx {
public static void main(String[] args) {
try {
install();
}catch(InstallException e) { // InstallException은 SpaceException과 MemoryException의 조상)
e.printStackTrace();
}catch(Exception e) {
e.printStackTrace();
}
}
static void install() throws InstallException{
try {
startInstall(); //프로그램 설치에 필요한 준비를 한다.
copyFiles();
}catch(SpaceException se) {
InstallException ie = new InstallException("설치 중 예외발생");
ie.initCause(se);
throw ie;
}catch(MemoryException me) {
InstallException ie = new InstallException("설치 중 예외발생");
ie.initCause(me);
throw ie;
}finally {
deleteTempFiles(); // 프로그램에 설치에 사용된 임시 파일들 삭제한다.
}
}
static void startInstall() throws SpaceException, MemoryException {
if( !enoughSpace() ) { // 충분한 설치 공간이 없으면
throw new SpaceException("설치할 공간이 부족합니다.");
}
if( !enoughMemory() ) {
throw new MemoryException("메모리가 부족합니다.");
// throw new RuntimeException(new MemoryException("메모리가 부족합니다."));
}
}
static void copyFiles() { /* 파일들 복사하는 코드*/}
static void deleteTempFiles() { /* 임시파일들 삭제하는 코드*/}
static boolean enoughSpace() {
// 설피하는데 필요한 공간이 있는지 확인하는코드
return false;
}
static boolean enoughMemory() {
// 설피하는데 필요한 메모리 공간이 있는지 확인하는코드
return true;
}
}
class InstallException extends Exception {
InstallException(String msg) {
super(msg);
}
}
class SpaceException extends Exception {
SpaceException(String msg) {
super(msg);
}
}
class MemoryException extends Exception {
MemoryException(String msg) {
super(msg);
}
}
Note) 실행 결과
- throw new RuntimeException(new MemoryException("메모리가 부족합니다."));
-> MemoryException은 Exception의 자손이므로 반드시 예외처리를 해야한다.
-> 이 예외를 RuntimeException으로 감쌌기 때문에 unchecked예외가 되었다.
'Java > Java의 정석' 카테고리의 다른 글
Chapter 10 날짜와 시간 & 형식화 date, time and formatting (0) | 2022.02.28 |
---|---|
Chapter 09 java.lang 패키지와 유용한 클래스 (0) | 2022.02.24 |
Chapter 07 객체지향 프로그래밍 II (0) | 2022.02.22 |
Chapter 06 객체지향 프로그래밍 I (0) | 2022.02.22 |
Chapter 05 배열(Array) (0) | 2022.02.20 |