티스토리 뷰

개인공부/JAVA

Exception

날따라해봐요요롷게 2021. 9. 9. 15:13

출처 (백기선의 자바 스터디 : https://www.notion.so/3565a9689f714638af34125cbb8abbe8)

  • 자바에서 예외 처리 방법 (try, catch, throw, throws, finally)
  • 자바가 제공하는 예외 계층 구조
  • Exception과 Error의 차이는?
  • RuntimeException과 RE가 아닌 것의 차이는?
  • 커스텀한 예외 만드는 방법

## 자바가 제공하는 예외 계층 구조, Exception과 Error의 차이

 

 

예외란?

 : 자바에서 예외는 에러(Error)와 예외(Exception) 두 가지 개념을 말한다.

 

오류(Error) 는 시스템 실행 중 생기는 오류를 말한다.

해당 오류는 개발자가 미리 예측을 하여 처리할 수 있는 오류가 아니다.

따라서 해당 오류를  처리하고자 한다고 해서 처리할 수 없다.

 

예외(Exception)는 개발자가 작성한 코드 로직에서 발생하는 오류이다.

해당 오류는 개발자가 미리 오류를 예측하여 처리할 수 있다. 따라서 이에 따른 예외를 알고 예외처리를 할 줄 알아야한다.

 

### 예외 클래스 계층 구조

JAVA 예외 계층 구조

자바는 Object 클래스가 최상위 계층으로 나머지 클래스들이 이를 상속받는 언어이다.

예외 또한 다른 클래스와 다르지 않게 Object 클래스를 상속받는다.

 

위의 계층구조를 살펴보면 모든 예외와 오류 클래스는 Throwable 클래스를 상속받는다.

Throwable 클래스를 상속받는 클래스를 살펴보면 Error와 Exception 2개의 클래스가 있다.

 

Exception 클래스를 살펴보자.

Exception 클래스는 Checked Exception과, Unchecked Exception 2가지로 나눠진다. 나눠지는 기준은 해당 Exception이 Runtime Exception이냐 아니냐로 나눠진다. 즉, Runtime Exception의 자식 클래스는 모두 Unchecked Exception 이며, Runtime Exception 에 속하지 않는 Exception은 Checked Exception이다.

 

구분 Checked Exception Unchecked Exception
처리여부 반드시 예외처리를 해야함 명시적인 처리를 강제하지 않음
확인 시점 컴파일 시점 실행 시점
대표 예외 -IOException
-SQLException
...
- NullPointException
- IndexOutOfBoundException
- IllegalArgumentException

 

두 Exception 을 나누는 명확한 기준은 "처리 하느냐" 에 있다.

 

- Checked Exception 이 일어날 메소드들은 반드시 처리를 해줘야 한다. (처리란 try ~ catch구문, throw를 말한다.)

즉, Checked Exception은 반드시 처리해야 하는 예외이다.

 

- Unchecked Exception 은 명시적인 예외처리를 하지 않아도 된다.

해당 예외는 피할 수 있지만, 대체로 개발자의 부주의한 로직으로 인하여 발생하기 때문에 미리 예측하지 못하기에 반드시 예외처리를 하지 않아도 된다.

 

public class Exception1 {
	public static void main(String[] args) {
		int[] arg = new int[3];
		try {
			for(int i=0; i<=3; i++) {
				arg[i] = i;
			}
		}catch(ArrayIndexOutOfBoundsException e) {
			System.out.println(e);
		}
	}
}

-----------------------------------------------------------------
public static void throwsException(int millisecond) throws IOException{
	File file = new File("/test.txt");
	boolean b = file.createNewFile();
}

 

## RuntimeException과 RE가 아닌 것의 차이는?

※ 컴파일 에러, 런타임 에러

 

컴파일이란

 : 코딩시 작성된 언어가 컴퓨터가 인식할 수 있는 기계어로 변환되어 프로그램이 실행 될 수 있도록 하는 과정을 말한다. 

컴파일 에러는 대체로 구문상 오류로 인해 프로그램이 컴파일 되지 못한다.

- 구문 마지막에 ; 를 누락한 경우 (syntax 에러)

- 괄호가 안맞는 경우

- classpath시 경로가 맞지 않는 경우

 

 

런타임이란

 : 컴파일 과정을 마친 응용 프로그램이 사용자에 의해 실행 되어 지는 때를 말한다.

런타임 에러란 프로그램이 컴파일 된 후 실행 시 발생하는 오류를 말한다. 대체로 개발자의 로직이 문제가 된다.

따라서 해당 오류 발생 시 발생한 예외를 보고 개발자가 직접 해당 오류를 추적하여 디버깅 해야한다.

- NullPointerException

- 0으로 나누는 경우

 

그렇다면 Checked, 와 Unchecked 로 나누는 이유는?

 : 복구가 어려운 경우, 코드로 할 수 없는 경우는 런타임 예외로 --> Unchecked 

 : 부가적인 작업을 할 수 있는 경우는 컴파일 에러로 --> Checked

 


## 자바에서 예외 처리 방법 (try, catch, throw, throws, finally)

 

예외를 처리하는 방법은 3가지가 있다.

  • 예외 복구 : 예외가 발생하면 다른 작업으로 유도한다.
  • 예외처리 회피 : 예외가 발생하면 처리 하지 않고 호출한 쪽으로 예외를 던져준다.
  • 예외 전환 : 호출한 쪽으로 예외를 던질 때 명확한 의미를 전달하기 위해 다른 예외로 전환하여 던진다.

 

#예외복구

 : 예외 복구는 예외가 발생하더라도 어플리케이션이 정상적으로 진행이 된다는 것이다.

예외 복구는 네트워크가 환경이 좋지 않아서 서버에 접속이 안되는 상황을 시스템에 적용하면 효율적이다.

예외가 발생하면 그 예외를 잡아서 일정 시간만큼 대기하고 다시 재시도를 반복한다.

그리고, 최대 재시도 횟수를 넘기면 예외를 발생시킨다.

재시도를 통해 정상적인 흐름을 타게 한다거나, 예외가 발생하면 이를 미리 예측하여 다른 흐름으로 유도시키도록 구현하면 비록 예외가 발생하였어도 정상적으로 작업을 종료할 수 있을 것이다.

 

# 예외처리 회피 ( throws)

 : 예외처리 회피는 신중하게 적용되어야 한다.

예외처리 회피는 예외 발생 시 throws를 통해 호출한 쪽으로 예외를 던지고 그 처리를 회피하는 것이다.

하지만 무책임하게 던지는 것은 위험하다. 호출한 쪽에서 다시 예외를 받아 처리하도록 하거나, 해당 메소드에서 이 예외를 던지는 것이 최선의 방법이라는 확신이 있을 때만 사용해야 한다.

 

#예외 전환 (catch)

 : 예외를 잡아서(catch) 다른 예외를 던지는 것이다.

호출한 쪽에서 예외를 받아서 처리할 때 조금 더 명확히 인지할 수 있도록 돕기 위한 방법이다.

어떤 예외인지 분명해야 처리가 수월해지기 때문이다.

예를 들어 Checked Exception중 복구가 불가능한 예외가 잡혔다면, 이를 Unchecked Exception으로 전환하여 다른 계층에서 일일이 예외를 선언할 필요가 없도록 할 수도 있다.

 

## 예외처리 방법 (try ~ catch ~ finally, throw throws)

 

try{
	예외 발생 가능성이 있는 코드
} catch(예외 타입1 매개변수) {
	예외 타입1의 예외가 발생 할 경우 처리하는 코드
} catch(예외 타입2 매개변수) {
	예외 타입2의 예외가 발생 할 경우 처리하는 코드
}finally {
	반드시 처리해야하는 코드
}

# try

- try 블록은 예외가 발생할 가능성이 있는 범위를 지정하는 블록이다.

- try 블록은 반드시 하나 이상의 catch 블록과 함께 구성되어야 한다.

 

# catch

- 블록은 매개변수의 예외 객체가 발생했을 때 참조하는 변수명으로, 예외 타입은 반드시 java.lang.Throwable 클래스의 하위 클래스 타입으로 선언되어야 한다.

- 예외 타입으로 작성한 예외 객체가 발생하면 try 블록의 나머지 문장을 수행하지 않고, JVM은 발생한 예외 객체를 발생시키며 예외 객체 타입이 동일한 catch 블록을 수행한다.

  ★ catch 블록은 여러개의 블록으로 구성될 수 있다. 단 주의사항이 있다.

catch 블록의 예외 타입 작성 시 부모 클래스가 자식 클래스보다 먼저 작성될 경우 컴파일 에러가 발생한다.

아래 코드는 부모 클래스가 자식 클래스보다 먼저 작성되어 컴파일 에러를 발생시키는 경우이다.

	public static void main(String[] args) {
		int[] arg = new int[3];
		try {
			System.out.println(arg[3]);
		}catch(Exception e) {
			System.out.println(e);
		}catch(ArrayIndexOutOfBoundsException e2) {
			System.out.println(e2);
	}

      - 계층구조, 상속관계의 따른 순서로 catch 작성

        (ArrayIndexOutOfBoundsException 은 Exception 클래스를 상속받는다.)

      - 좁은 그물망일 수록 먼저 작성을 한다.

       (ArrayIndexOutOfBoundsException 를 Exception 클래스보다 먼저 작성을 한다.)

 

    ★여러개의 catch 블록이 작성되어 해당 catch 구문이 동일한 행위를 처리한다면 (Multicatch Block)

    catch 블록을 묶어서 처리한다.

    - Multicatch Block 으로 묶어 처리하는 경우 예외 타입이 상속관계인 경우는 ---> 처리될 수 없다. (불가능!!)

try{
	.. do something
}catch(IllegalStateException | IllegalArugmentException e){
	// catch !!
}

 

# finally

- 반드시 적어야 하는 블록은 아니다.

- finally 블록에 작성된 코드는 try ~ catch 구문의 예외 발생과는 상관없이 반드시 실행된다.

- 많이 활용이 되는 코드는 파일을 사용 후에는 반드시 닫아야 하기에 finally 구문에 파일을 다는 코드를 작성한다.

 

finally 코드로 인한 try ~ catch (return 포함) 구문의 실행 순서

- return IN try : finally 블록을 실행 후 retrun 이 실행

- return IN catch: finally 블록을 실행 후 retrun 이 실행

- return IN finally : try 블록 안에 발생한 예외를 무시하고 finally 블록을 수행 후 종료된다. (발생된 예외를 알 수 없음)

따라서 finally 안에는 return 구문을 사용하지 않는다!

 

# Throws

 : 예외가 발생한 메소드를 호출한 곳으로 예외 객체를 넘기는 방법

자바의 예외 처리 방법에는 예외가 발생한 지점에서 try-catch, try-catch-finally 블록을 이용하여 예외를 직접 처리하지 않고 예외가 발생한 메소드를 호출한 지점으로 예외를 전달하여 처리하는 방법이 있다.

--> 이때 사용되는 예약어가 Throws 이다.

 

public class Test{
	static void callDriver() throws ClassNotFoundException{
		Class.forName("oracle.jdbc.driver.OracleDriver");
		System.out.println("완료");
	}

	public static void main(String[] args){
		try{
			callDriver();
		}catch(ClassNotFoundException e){
			System.out.println("클래스를 찾을 수 없습니다.");
		}finally{
			System.out.println("시스템 종료");
		}
	}
}

 

# Throw

 : 인위적으로 예외를 발생시키는 예약어

개발자가 의도한 케이스에 대해서 의도적으로 예외를 발생시키고자 할 때 사용된다. 

특정 예외를 만났을 때 더욱 구체적인 예외로 처리하고자 할 때에도 사용된다.

List<String> values = getValues();
if(CollectionUtils.isEmpty(values)){
	throw new IllegalStateException("....");
}

#try - with - resources

 : exception 시 resources 를 자동으로 close() 해준다.

- 해당 로직을 작성 시 객체는 AutoCloseable 인터페이스를 구현한 객체여야한다.

 

- AutoCloseable 인터페이스

public interface AutoCloseable{
	void close() throws Exception;
}

AutoCloseable 인터페이스는 close() 메서드를 포함하고 있다. 인터페이스를 구현한 클래스는 close() 메서드를 명시적으로 호출하지 않아도 close() 메서드 부분이 호출된다.

FileInputStream 클래스는 Closeable과 AutoCloseable 인터페이스를 구현한다.

따라서 자바7 이후에는 FileInputStream 사용시에 따로 close() 메서드를 호출하지 않아도 된다.

 

AutoCloseable 인터페이스가 호출되는 과정을 알아보자.

AutoCloseable 인터페이스를 구현 시 close() 메서드를 반드시 오버라이딩 해야한다.

public class AutoCloseObj implements AutoCloseable {
	@Override
	public void close() throws Exception {
		System.out.println("resources --> close() 실행");
	}
}
-------------------------------------------------------------
public class AutoCloseTest {
	public static void main(String[] args) {
		try(AutoCloseObj obj = new AutoCloseObj()){
		}catch (Exception e) {
			System.out.println("예외 발생!");
		}
	}
}

output  :  resources --> close() 실행

try-with-resources 를 구현하는 방법은

try문의 괄호안에 사용하는 리소스를 선언한다. (Java7)

소스를 여러 개 생성할 경우 ';' 세미 콜론으로 구분하여 사용한다.

public class AutoCloseTest {
	public static void main(String[] args) {
		try(AutoCloseObj obj = new AutoCloseObj()){
			throw new Exception();
		}catch (Exception e) {
			System.out.println("예외 발생!");
		}
	}
}

output : 
resources --> close() 실행
예외 발생!

예외가 발생하는 경우를 살펴보면 close() 메서를 먼저 호출하여 리소스를 닫은 후 예외처리를 하게 된다.

 

향상된 try-with-resources (java9)

위의 예제를 살펴보면 (java7) try문 괄호안에 AutoCloseable 인터페이스를 구현한 리소스의 변수 선언을 try문 안에서 해야했다. 이는 리소스가 외부에 선언되고 생성된 경우에도 다른참조 변수로 괄호 안에 다시 선언 해야 했다.

 

    	AutoCloseObj obj = new AutoCloseObj()
		try(AutoCloseObj obj2 = obj){ // 다른 참조 변수로 다시 선언해야 한다.
			throw new Exception();
		}catch (Exception e) {
			System.out.println("예외 발생!");
		}
	}
    (java7)
    ===============================================================
    
    (java9) : 외부에서 선언한 변수를 그대로 사용할 수 있다.
    	AutoCloseObj obj = new AutoCloseObj()
		try(obj){
			throw new Exception();
		}catch (Exception e) {
			System.out.println("예외 발생!");

 

## 커스텀 예외

(출처  : https://m.blog.naver.com/sthwin/221144722072)

 

4 Best Practices for Custom Exceptions (커스텀 예외 생성 시 주의 할 4가지)

  • Always Provide a Benefit
  • Follow the Naming Convention
  • Provide Javadoc Comments for Your Exception Class
  • Provide a Constructor That Sets the Cause

# Always Provide a Benefit

 : 커스텀 예외는 표준 예외가 포함하고 있지 않은 정보나 기능을 제공한다.

하지만! 기존 표준 예외에 비해 커스텀 예외가 어떠한 장점을 제공하지 못한다면 표준 예외만 하지 못한다.

이럴 경우 표준예외들 중 에서 하나를 사용하는 것이 낫다. 모든 자바 개발자들은 이 예외를 알고 있으며 코드와 API를 더 쉽게 이해할 수 있도록 해준다.

따라서 표준 예외와 비교하여 반드시 Benefit이 있는 경우에 커스텀 예외를 설정한다.

 

# Follow the Naming Convention

 : 네이밍 규칙을 따라라

자바에서 제공한 예외 클래스를 살펴보면 모두 "Exception"으로 끝나는 것을 볼 수 있다.개인이 만드는 커스텀 예외 또한 다른 개발자들이 쉽게 예외임을 인지할 수 있도록 "Exception"을 넣어서 네이밍을 해주는 것이 좋다.

 

# Provide Javadoc Comments for Your Exception Class

 : 커스텀 예외 반드시! Comments 만들어 주기

많은 커스텀 예외들이 Javadoc Comments 없이 만들어지는 경우가 많다.

문서화 되지 않은 API는 사용이 매우 어렵기에 개발자의 API 의 모든 클래스, 맴버변수, 생성자들에 대해서는 문서화하는 것이 일반적인 Practice 이다.

예외 클래스들은 우리의 API에 크게 드러나지 않는 부분 일 수도 있으나 실상은 그렇지 않다. 클라이언트와 직접 관련된 메소드들 중 하나가 예외를 던지면 그 예외는 바로 예외의 일부가 된다. 그렇다는 것은 잘 만들어진 JavaDoc와 문서화가 필요하다는 뜻이다.
Javadoc은 예외가 발생할 수도 있는 상황과 예외의 일반적인 의미를 기술한다. 목적은 다른 개발자들이 우리의 API를 이해하도록 하고 일반적인 에러상황들을 피하도록 돕는것이다.

 

# Provide a Constructor That Sets the Cause

 : 예외 발생 시 예외의 원인을 담고 있는 생성자를 제공하자!

커스텀 예외를 만들어 실행 시 커스텀 예외를 전지기 전에 표준 예외를 캐치하는 경우가 많다.

보통 캐치된 예외에는 실행 시 발생한 오류를 분석하는데 필요한 정보가 포함되어 있다.

 

아래의 예제를 보면 NumberFormatException은 에러에 대한 상세 정보를 제공한다. MyBusinessException의 cause처럼 cause정보를 설정하지 않으면 이 정보를 잃을 것이다.

public void wrapException(String input) throws MyBusinessException {
    try {
        // do something
    } catch (NumberFormatException e) {
        throw new MyBusinessException("A message that describes the error.", e, ErrorCode.INVALID_PORT_CONFIGURATION);
    }
}

 

## Exception과 RuntimeException 

 : Exception과 RuntimeException은 예외의 원인을 기술하고 있는 Throwable 을 받을 수 있는 생성자 메소드를 제공한다. 만들고자 하는 커스텀 예외도 이렇게 하는 것이 좋다.

발생한 Throwable를 파라미터를 통해 가져올 수 있는 생성자를 최소한 하나를 구현하고 수퍼클래스에 Throwable을 전달해줘야 한다.

public class MyBusinessException extends Exception {
    public MyBusinessException(String message, Throwable cause, ErrorCode code) {
            super(message, cause);
            this.code = code;
        }
        ...
}

 

## Implementing a Custom Exception (Checked Exception)

 : Checked Exception을 구현하기 위해서는 Exception 클래스를 상속받아야 하는데, 커스텀 예외를 구현하기 위해 필요한 유일한 필수사항이다. (class ~~ extends Exception)

 

위에 4가지 Best Practices에서 설명했듯이 발생한 예외를 생성자에 주입하기 위한 생성자 메소드를 제공해야 하며, 표준 예외보다 더 나은 이점들을 제공해야 한다.

아래 예제는 설명해 온 것들을 보여준다.

  • 예외를 기술하는 Javadoc 주석을 추가
  • 수퍼클래스에 발생한 예외를 주입하는 생성자 메소드를 구현
  • 표준 예외보다 더 나은 장점을 제공하기 위해 MyBusinessException은 문제 식별을 위한 에러코드를 저장하는 커스텀 enumeration을 사용
  • 클라이언트들은 에러메세지를 보여주기 위해 이 코드를 사용할 수 있으며, support ticket 내에 이 코드를 포함하도록 유도
/**
 * The MyBusinessException wraps all checked standard Java exception and enriches them with a custom error code.
 * You can use this code to retrieve localized error messages and to link to our online documentation.
 * 
 * @author TJanssen
 */
public class MyBusinessException extends Exception {
    private static final long serialVersionUID = 7718828512143293558 L;
    private final ErrorCode code;
    public MyBusinessException(ErrorCode code) {
        super();
        this.code = code;
    }
    public MyBusinessException(String message, Throwable cause, ErrorCode code) {
        super(message, cause);
        this.code = code;
    }
    public MyBusinessException(String message, ErrorCode code) {
        super(message);
        this.code = code;
    }
    public MyBusinessException(Throwable cause, ErrorCode code) {
        super(cause);
        this.code = code;
    }
    public ErrorCode getCode() {
        return this.code;
    }
}

커스텀 예외를 만들었다면 실행 코드에서 MyBusinessException을 던질 수 도 있고 또는 메소드 시그니쳐에 표기할 수 도 있고 try-cache절에서 처리할 수도 있다.

public void handleExceptionInOneBlock() {
    try {
        wrapException(new String("99999999"));
    } catch (MyBusinessException e) {
        // handle exception
        log.error(e);
    }
}
private void wrapException(String input) throws MyBusinessException {
    try {
        // do something
    } catch (NumberFormatException e) {
        throw new MyBusinessException("A message that describes the error.", e, ErrorCode.INVALID_PORT_CONFIGURATION);
    }
}

 

## Implementing an Unchecked Exception

 : Custom Unchecked Exception 예외 구현은 checked exception 예외 구현과 동일하다.

한가지 차이가 있다면 "Exception"을 확장하는 것이 아닌 "RuntimeException"을 확장한다.

(class ~~ extends RuntimeException)

 

```java
/**
 * The MyUncheckedBusinessException wraps all unchecked standard Java exception and enriches them with a custom error code.
 * You can use this code to retrieve localized error messages and to link to our online documentation.
 * 
 * @author TJanssen
 */
public class MyUncheckedBusinessException extends RuntimeException {
    private static final long serialVersionUID = -8460356990632230194 L;
    private final ErrorCode code;
    public MyUncheckedBusinessException(ErrorCode code) {
        super();
        this.code = code;
    }
    public MyUncheckedBusinessException(String message, Throwable cause, ErrorCode code) {
        super(message, cause);
        this.code = code;
    }
    public MyUncheckedBusinessException(String message, ErrorCode code) {
        super(message);
        this.code = code;
    }
    public MyUncheckedBusinessException(Throwable cause, ErrorCode code) {
        super(cause);
        this.code = code;
    }
    public ErrorCode getCode() {
        return this.code;
    }
}

 

다른 unchecked예외를 사용하는 것 처럼 MyUncheckedBusinessException를 사용할 수 있다. 코드에서 이 예외를 던질 수 있으며, 캐치절에서 이 예외를 사용할 수있다. 

private void wrapException(String input) {
    try {
        // do something
    } catch (NumberFormatException e) {
        throw new MyUncheckedBusinessException("A message that describes the error.", e, ErrorCode.INVALID_PORT_CONFIGURATION);
    }
}

커스텀 예외 만들기 Summary

 

Checked Exception을 구현할 때는 Exception 을 확장

Unchecked Exception을 구현할 때는 RuntimeException 을 확장.

 

4가지 Best Practice

  1. Java 표준 예외를 사용하는 것 보다 작성한 Custom 예외를 사용하는게 더 많은 이익을 얻는다고 생각할 경우에만 Custom Exception을 구현하자.
  2. 작성한 Custom Exception 클래스의 이름의 끝은 "Exception"으로 끝나도록 하자.
  3. API 메소드가 어떤 하나의 예외를 기술하고 있다면, 그 예외는 API의 한 부분이 되는 것이며 그 예외를 문서화 해야 한다.
  4. 예외의 Cause 를 설정할 수 있는 생성자를 제공해야 한다.

## 예외의 전파

 

 

'개인공부 > JAVA' 카테고리의 다른 글

Exception - throws (학교공부)  (0) 2021.09.17
Thread - Sync  (0) 2021.09.10
배열, 기본클래스  (0) 2021.05.05
JAVA - Inner Class, Lambda , Stream  (0) 2021.03.21
JAVA - CLASS  (0) 2021.03.16
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
글 보관함