예외처리 - Java 에서

박영준·2023년 7월 6일
0

Java

목록 보기
105/112

1. try - catch 문

1) 정의

  • Java 에서 제공하는 예외처리 방법

    그러나, 예외 처리를 위해 모든 코드에 try-catch 를 붙이는 것은 비효율적이다.

2) 주의점

  • try/catch 로 예외를 잡고 아무 조치도 하지 않는 것은 다음과 같은 이유가 있는게 아니라면 반드시 피해야 한다.
    (= 오류가 있어서 예외가 발생했는데, 그것을 무시하고 진행하는 경우와 비슷)

    • 예외가 발생하여도 무관하며,
    • 다음 라인을 실행하겠다는 의도

→ 어떤 기능이 비정상적으로 동작하거나, 메모리나 리소스가 고갈되는 등의 문제를 야기할 수 있다.

→ 따라서 예외 처리 시에는 빈 값을 반환하는 등...의 조치를 통해 상황을 적절하게 복구 or 작업을 중단시키고 관리자에게 이를 전달하자

3) 사용법

문법

try {
	// 예외 발생할 가능성있는 문장
} catch (Exception1 e1) {
    // Exception1 e1 예외 발생할 경우, 이를 처리하기 위한 문장
} catch (Exception2 e2) {
	// Exception1 e2 예외 발생할 경우, 이를 처리하기 위한 문장
} catch (ExceptionN eN) {
	// Exception1 eN 예외 발생할 경우, 이를 처리하기 위한 문장
}
  1. try 블럭을 수행
  2. 이 中 발생한 예외와 일치하는 단 하나의 catch 블럭만을 수행
  3. 단, 발생한 예외와 일치하는 catch 블럭이 없다면, 예외는 처리되지 않음

예시 1 : 예외가 발생하지 않은 경우

class EX {
	public static void main(String args[]) {
    	System.out.println(1);
        
        try {
            System.out.println(2);
            System.out.println(3);
        } catch (Exception e) {
            System.out.println(4);		// 실행 X
        }

        System.out.println(5);
	}
}

/* 출력 결과
1
2
3
5
*/

예시 2 : 예외가 발생한 경우

class EX {
	public static void main(String args[]) {
    	System.out.println(1);
        
        try {
            System.out.println(0/0);		// 고의로 ArithmeticException 예외를 발생시켰을 때
            System.out.println(2);			// 실행 X
        } catch (ArithmeticException ae) {	// 예외가 실행됨
            System.out.println(3);
        }

        System.out.println(4);
	}        
}

/* 출력 결과
1
3
4
*/
  1. try 블럭을 수행
    그러나, try 블럭에서 예외가 발생했으므로 해당 블럭을 바로 벗어난다
  2. catch 블럭으로 와서, 블럭 안의 문장을 수행

catch 블럭에서 ArithmeticException ae 대신, Exception e 를 사용한다면?
ArithmeticException클래스는 Exception클래스의 자식이다.
따라서, 결과적으로는 동일한 출력 결과가 나오게 된다.

ArithmeticException 예외가 발생할 때, ArithmeticException 인스턴스가 생성된다.
따라서 catch (ArithmeticException ae) 은 사실상 if (e instanceOf ae) 의 결과가 true 이므로
ArithmeticException를 대신하여 Exception 사용할 수 있는 것이다.

예시 3 : 하나의 예와 & 나머지 모든 예외

class EX {
	public static void main(String args[]) {
    	System.out.println(1);
        
        try {
            System.out.println(0/0);		// 고의로 ArithmeticException 예외를 발생시켰을 때
            System.out.println(2);			// 실행 X
        } catch (ArithmeticException ae) {	// 예외가 실행됨
            if (ae instatnceof ArithmeticException)
                System.out.println("true");
            System.out.println("ArithmeticException");
        } catch (Exception e) {				// ArithmeticException 를 제외한 모든 예외를 처리
            System.out.println("Exception");
        }

        System.out.println(3);
	}        
}

/* 출력 결과
1
true
ArithmeticException3
3
*/

예시 4 : 예외 처리와 return문

try {
	System.out.println("예외처리1");
    return 0;
} catch (Exception e) {
	e.printStackTrace();
    return 1;
} finally {
	System.out.println("예외처리2");
}
  • 예외가 발생할 경우, 값을 반환하기 위해 catch블럭에도 return문이 있어야 한다

2. 멀티 catch 블럭

기존 문법

try {
	// 예외 발생할 가능성있는 문장
} catch (Exception1 e) {
    // Exception1 e1 예외 발생할 경우, 이를 처리하기 위한 문장
} catch (Exception2 e2) {
	// Exception1 e2 예외 발생할 경우, 이를 처리하기 위한 문장
}

멀티 catch 블럭 문법

try {
	// 예외 발생할 가능성있는 문장
} catch (Exception1 | Exception2 e1) {
    // Exception1 e1 예외 발생할 경우, 이를 처리하기 위한 문장
} catch () {
	// Exception1 e2 예외 발생할 경우, 이를 처리하기 위한 문장
}
  • | 기호는 계속해서 붙일 수 있다

  • 참조변수 'e'를 사용한 이유?

    • | 기호로 연결된 예외 클래스들의 공통 분모인 부모 예외 클래스에 선언된 멤버만 사용 가능하기 때문
  • 주의!

    • | 로 연결된 예외 클래스가 서로 부모-자식 관계라면, 컴파일 에러가 발생
      • 이 경우, '부모 클래스 | 자식 클래스' 과 '부모 클래스' 하나만 사용하는 것과 의미가 동일하기 때문
  • 단점

    • | 기호로 연결된 예외 클래스들이 있을 때, 실제로 어떤 예외가 발생한 것인지까지는 알 수가 없게 된다

3. try - catch - finally

문법

try {
	// 예외 발생할 가능성있는 문장
} catch (Exception1 e1) {
	// Exception1 e1 예외 발생할 경우, 이를 처리하기 위한 문장
} finally {
	// 예외 발생과 무관하게, 항상 수행될 문장
}
  • finally은 try - catch문의 마지막에 위치한다
  • 만약 예외가 발생하지 않는다면, try -> finally 순이 된다

예시 1

// 기존 코드
try {
	install();
    copy();
    delete();			// 중복된 코드
} catch (Exception1 e) {
	e.printStackTrace();
    delete();			// 중복된 코드
}

// finally를 사용한 코드
try {
	install();
    copy();
} catch (Exception1 e) {
	e.printStackTrace();
} finally {
	delete();
}
  • try블럭(예외가 발생하지 않았을 때)과 catch블럭(예외가 발생했을 때)에서 모두 수행되어야할 코드를 finally블럭에 위치시킨다

예시 2

class Ex {
	static void method(boolean b) {

		try {
			System.out.println(1);

			if (b)
				throw new ArithmeticException();
			System.out.println(2);

		} catch (RuntimeException r) {
			System.out.println(3);
			return;
		} catch (Exception e) {
			System.out.println(4);
			return;
		} finally {
			System.out.println(5);
		}
        
		System.out.println(6);
	}

	public static void main(String[] args) {
		method(true);
		method(false);
	}
}
/* 출력 결과
1
3
5
1
2
5
6
*/
  1. main메소드의 method(true)가 실행돼서, method(boolean b)가 실행됨
  2. 1 출력
  3. true 이므로, ArithmeticException 예외를 발생시켜, catch (RuntimeException r) 문으로 이동하여 3 출력
    • ArithmeticException 클래스는 RuntimeException 클래스의 자식이기 때문
  4. finally문으로 이동해서, 5 출력
  5. main메소드의 method(false)가 실행돼서, method(boolean b)가 실행됨
  6. 1 출력
  7. false 이므로, if 조건문에 있는 예외가 실행되지 않고, 2 출력
  8. finally문으로 이동해서, 5 출력
  9. try-catch-finally문을 벗어나서, 6 출력

주의!
System.exit() 메서드를 만나게 될 경우,
아무리 finally 이라하더라도 실행되지 않고, 바로 강제 종료된다.
참고: System.exit() 자바 강제 종료

4. throw 키워드

class EX {
	public static void main(String args[]) {
        
      try {
          Exception e = new Exception("예외 발생!")		
          throw e;

      } catch (Exception e) {	// 예외가 실행됨
          ...
      }

      System.out.println("프로그램 정상 종료!");
	}      
}
  • throw 를 사용하면, 고의로 예외를 발생시킬 수 있다

  • throw new Exception("예외 발생!");

    • Exception e = new Exception("예외 발생!") 과 throw e 를 한 줄로 줄일 수 있다
      • Exception e = new Exception() : new 연산자로, 발생시킬 예외 클래스의 객체를 만든다
      • throw e : 예외를 발생시킨다

5. 메서드에서 예외처리

1) 주의점

① 무분별한 throws Exception

public void method1() throws Exception {
    method2();
}

public void method2() throws Exception {
    method3();
}

public void method3() throws Exception {

}
  1. 무분별한 throws Exception 은 해당 문구를 보고 여기서 어떤 문제가 발생할 수 있는지와 같은 의미있는 정보를 얻을 수 X

  2. 이 메소드를 다른 메소드에서 사용중이라면, throws Exception 이 전파돼버린다.

→ 복구가 불가능한 예외들( SQLException 같이)이라면,
기계적으로 throws를 던지지 않고 가능한 빨리 언체크/런타임 예외로 전환해주자.

② 무책임한 throws

컴파일 에러를 피하기위해 무분별한 throws들을 적는 경우가 있다.
(모든 예외를 무조건 던져버리는 throws Exception을 모든 메소드에 기계적으로 넣는 등...)

그 결과로
1. 해당 예외 처리의 역할(실행 중 예외 상황이 발생하는지, 습관적으로 붙여놓은지...)을 확인 X
2. 적절한 처리를 통해 복구될 수 있는 예외 상황도 제대로 처리 X

2) 사용법

문법

void method() throws Exception1, Exception2, ... ExceptionN {
	// 메서드 내용
}
  • , 로 여러개의 예외를 적어줄 수도 있다.

예시 1 : 호출한 메서드에게 예외 넘기기 1

class EX {
	public static void main(String args[]) throws Exception {
		method1();
	}
    
    static void method1() throws Exception {
    	method2();
    }
    
    static void method2() throws Exception {
    	throw new Exception();
    }
}
  1. throw new Exception() 에 의해 예외가 강제적으로 발생
    • 이때, main메서드, method1메서드, method2메서드는 호출 스택에 있다
    • 이 中 method2()가 가장 위에 있다(가장 마지막에 추가 됐기 때문)
  2. 그러나, try-catch문이 없으므로 method2는 종료되고, 자신을 호출한 method1()에게 예외를 넘긴다
  3. method1()도 예외처리를 해주지 않으므로, main메서드에게 예외를 넘긴다
  4. main메서드도 에외처리를 해주지 않으므로, 프로그램은 비정상적으로 종료된다

예시 2 : 호출한 메서드에게 예외 넘기기 1

class Ex {
	public static void main(String[] args) {

		try {
			method1();
		} catch (Exception e) {
			System.out.println(5);
		}
	}

	static void method1() {

		try {
			method2();
			System.out.println(1);			// 주의! 해당 줄을 출력하진 않음
		} catch (ArithmeticException e) {
			System.out.println(2);
		} finally {
			System.out.println(3);
		}

		System.out.println(4);
	} 

	static void method2() {
		throw new NullPointerException();
	}
}

/* 출력 결과
3
5
*/
  1. main메소드에서 try블럭이 실행돼서, method1()을 호출함
  2. method1메서드가 호출돼서, try블럭이 실행되는데, method2()을 호출함
  3. method2메서드가 호출돼서, NullPointerException 예외를 발생시킴
    그러나 예외처리를 해줄 수 없어, 예외는 자신을 호출한 method1로 넘어감
  4. 그러나 method1에서도 예외처리를 해줄 수 없어, finally문으로 이동해서 3 출력하고,
    예외는 자신을 호출한 main메서드로 넘어감
  5. 예외는 catch (Exception e) 블럭에서 처리가 되고, 5 출력

예시 3 : 메서드 자체적으로 예외처리

class EX {
	public static void main(String args[]) throws Exception {
		
        try {
        	...
        } catch {
        	...
        }    
	}
    
    static File createFile(String fileName) throws Exception {
    	...
    	
    }
}
  • main메서드와 createFile()메서드가 있다고 할 때, createFile()메서드에서 예외가 발생한 경우
    • createFile()메서드에서 예외를 자체적으로 해결하지 못했으면 : 예외가 발생한 것을 main메서드에게 알리게 된다
    • createFile()메서드에서 예외를 자체적으로 해결했으면 : 해당 메서드 내에서 예외처리가 돼서, main메서드는 예외가 발생한 것조차 알지 못한다

주의!
'3) throw 키워드'와 '4) 메서드에서 예외처리에서의 throws' 를 혼동하지 말자.

6. 예외 되던지기

예시 1

class EX {
	public static void main(String args[]) {
    
        try {
        	method1();
        } catch (Exception e) {
            System.out.println("다시 예외처리!");
        }
    }
    
    static void method1 throws Exception {
    
    	try {
        	throw new Exception();
        } catch (Exception e) {
			System.out.println("예외처리!");
            throw e;			// 다시 예외를 발생시킨다
        }
    }
}
  • 하나의 예외에 대해서, 예외가 발생한 메서드 & 해당 메서드를 호출한 메서드 양쪽에서 처리하게 할 수도 있다.
    • 예외처리한 후(try-catch문), 고의적으로 다시 예외를 발생시킨다(throw)

예시 1

class Ex{
	public static void main(String[] args) {

		try {
			method1();
			System.out.println(6);
		} catch (Exception e) {
			System.out.println(7);
		}
	}

	static void method1() throws Exception {

		try {
			method2();
			System.out.println(1);
		} catch (NullPointerException e) {
			System.out.println(2);
			throw e;
		} catch (Exception e) {
			System.out.println(3);
		} finally {
			System.out.println(4);
		}

		System.out.println(5);
	} 

	static void method2() {
		throw new NullPointerException();
	}
}

/* 출력 결과
2
4
7
*/
  1. main메서드의 try문 실행해서, try블럭 안에 있는 method1() 호출
  2. method1메서드의 try문 실행해서, try블럭 안에 있는 method2( ) 호출
  3. method2메서드에서 NullPointerException 예외 발생
    그러나 자신은 예외처리를 할 수 없어서, 자신을 호출한 method1메서드로 넘긴다
  4. catch (NullPointerException e) 블럭이 실행돼서, 2 출력하고, thorw e 로 다시 예외를 발생시키고, finally블럭으로 4 출력
  5. throw e 로 다시 발생한 예외는 main메서드의 catch (Exception e) 블럭에서 처리돼서 7 출력

try-catch문에서 처리했다가 다시 발생시켜

main메서드에 예외를 전달합니다.

7. 연결된 예외

1) 정의

try {
	install();				// SpaceException 예외 발생
    copy();
} catch (SpaceException e) {
	InstallExcepion ie = new InstallExcepion("설치중 에외 발생");		// 예외 생성
    ie.initCause(e);												// InstallExcepion의 원인 예외를 SpaceException 예외로 지정
	throw ie;														// InstallExcepion 을 발생시킴
} catch (MemoryException me) {
	...
  • 한 예외(원인 예외)가 다른 예외를 발생시킨다.

    • Throwable initCause(Throwable cause) : 지정한 예외를 원인 예외로 등록
    • Throwable getCause() : 원인 예외를 반환
  • initCause 는 Exception클래스의 부모 클래스인 Throwable클래스에 정의돼있으므로, 모든 예외에서 사용 가능

2) 필요성

발생한 예외를 그냥 처리하지 않고,
원인 예외로 등록 후, 다시 예외를 발생시키는 원인은 무엇일까?

① 큰 예외로 묶기

여러가지 예외가 있을 때, 이들을 하나의 큰 예외로 묶기 위해서이다.
이 경우, 묶인 예외 간에는 상속 관계가 아니어도 된다.

② 체크 예외 → 언체크 예외

체크 예외

static void install() thorows SpaceException, MemoryException {		// MemoryException 은 Exception클래스의 자식이므로, 반드시 예외처리가 필요
	if (!enoughSpace()) {
    	throw new SpaceException("공간 부족!");
    }
    
    if (!enoughMemory()) {
    	throw new MemoryException("메모리 부족!");
    }
}

체크 예외로 예외처리를 한 것은 프로그래밍 경험이 적은 사람에게도 유용한 방법이기 때문이었다.
그러나 현재는 체크 예외로도 처리할 수 없는 예외들이 생겨났다.

언체크 예외

static void install() thorows SpaceException {		// MemoryException 가 언체크 예외가 됐으므로, 선언부에는 따로 선언해주지 않아도 된다
	if (!enoughSpace()) {
    	throw new SpaceException("공간 부족!");
    }
    
    if (!enoughMemory()) {
    	throw new RuntimeException(new MemoryException("메모리 부족!"));		// RuntimeException 이 MemoryException 을 감싼다 (언체크 예외가 됨)
    }
}

언체크 에외는 선택적인 예외처리가 가능하다.
따라서, 예외처리를 강요받지 않게 된다.

8. printStackTrack(), getMessage()

예시 1

class EX {
	public static void main(String args[]) {
    	System.out.println(1);
        
        try {
            System.out.println(0/0);
            System.out.println(2);
        } catch (ArithmeticException ae) {
            ae.printStackTrack();					// 참조변수 ae를 통해, 생성된 ArithmeticException 인스턴스에 접근
            System.out.println(ae.getMessage());	// 참조변수 ae를 통해, 생성된 ArithmeticException 인스턴스에 접근
        }

        System.out.println(4);
    }
}

/* 출력 결과
1
java.lang.ArithmeticException: / by zero		// printStackTrack() 의 결과
	at EX.main(EX.java:6)
/ by zero    									// getMessage() 의 결과
*/
  • printStackTrack()

    • 예외가 발생할 때, 호출 스택에 있었던 메서드의 정보 & 예외 메시지를 출력
  • getMessage()

    • 발생한 예외 클래스의 인스턴스에 저장된 메시지를 얻기
  • 출력 결과 설명

    • ArithmeticException : 발생한 예외 종류
    • main : 예외 발생 메서드의 위치
    • EX : EX 파일
    • java:6 : EX.java 파일의 6번째 줄

예시 2

/* 출력 결과
1
java.lang.ArithmeticException: / by zero
	at EX.method2(EX.java:12)
	at EX.method1(EX.java:8)
	at EX.main(EX.java:4)
/ by zero    									
*/
  • 출력 결과 설명
    • 맨 위에 있는 메서드(method2)가 현재 실행 중인 메서드
    • 아래 있는 메서드(method1)가 바로 위의 메서드를 호출한 것
    • 따라서, main → method1 → method2 순서로 호출되었다
profile
개발자로 거듭나기!

0개의 댓글